Merge "Revert "Use .KATI_RESTAT to reduce unnecessary rebuilds of .jar files""
diff --git a/core/Makefile b/core/Makefile
index 8922f06..c930708 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1629,6 +1629,9 @@
 	$(hide) for part in $(AB_OTA_PARTITIONS); do \
 	  echo "$${part}" >> $(zip_root)/META/ab_partitions.txt; \
 	done
+	$(hide) for conf in $(AB_OTA_POSTINSTALL_CONFIG); do \
+	  echo "$${conf}" >> $(zip_root)/META/postinstall_config.txt; \
+	done
 	@# Include the build type in META/misc_info.txt so the server can easily differentiate production builds.
 	$(hide) echo "build_type=$(TARGET_BUILD_VARIANT)" >> $(zip_root)/META/misc_info.txt
 ifdef OSRELEASED_DIRECTORY
diff --git a/core/binary.mk b/core/binary.mk
index d3b536d..028950a 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -730,8 +730,9 @@
 	$(transform-aidl-to-cpp)
 -include $(addsuffix .P,$(basename $(aidl_gen_cpp)))
 
-# Add generated headers to include path.
+# Add generated headers to include paths.
 my_c_includes += $(aidl_gen_include_root)
+my_export_c_include_dirs += $(aidl_gen_include_root)
 # Pick up the generated C++ files later for transformation to .o files.
 my_generated_sources += $(aidl_gen_cpp)
 
@@ -1301,7 +1302,10 @@
 export_includes := $(intermediates)/export_includes
 $(export_includes): PRIVATE_EXPORT_C_INCLUDE_DIRS := $(my_export_c_include_dirs)
 # Make sure .pb.h are already generated before any dependent source files get compiled.
-$(export_includes) : $(LOCAL_MODULE_MAKEFILE_DEP) $(proto_generated_headers) $(dbus_generated_headers)
+# Similarly, the generated DBus headers need to exist before we export their location.
+# People are not going to consume the aidl generated cpp file, but the cpp file is
+# generated after the headers, so this is a convenient way to ensure the headers exist.
+$(export_includes) : $(LOCAL_MODULE_MAKEFILE_DEP) $(proto_generated_headers) $(dbus_generated_headers) $(aidl_gen_cpp)
 	@echo Export includes file: $< -- $@
 	$(hide) mkdir -p $(dir $@) && rm -f $@
 ifdef my_export_c_include_dirs
diff --git a/core/clang/TARGET_arm64.mk b/core/clang/TARGET_arm64.mk
index 5120a6d..532ca77 100644
--- a/core/clang/TARGET_arm64.mk
+++ b/core/clang/TARGET_arm64.mk
@@ -66,6 +66,6 @@
 TARGET_LIBPROFILE_RT := $(LLVM_RTLIB_PATH)/libclang_rt.profile-aarch64-android.a
 
 # Address sanitizer clang config
-ADDRESS_SANITIZER_RUNTIME_LIBRARY := libclang_rt.asan-arm64-android
+ADDRESS_SANITIZER_RUNTIME_LIBRARY := libclang_rt.asan-aarch64-android
 ADDRESS_SANITIZER_RPATH := /data/vendor/lib64:/$(TARGET_COPY_OUT_VENDOR)/lib64:/data/lib64
 ADDRESS_SANITIZER_LINKER := /system/bin/linker_asan64
diff --git a/core/clang/config.mk b/core/clang/config.mk
index 03e8dd5..756bf49 100644
--- a/core/clang/config.mk
+++ b/core/clang/config.mk
@@ -1,7 +1,8 @@
 ## Clang configurations.
 
-LLVM_PREBUILTS_VERSION := 3.8
-LLVM_PREBUILTS_PATH := prebuilts/clang/host/$(BUILD_OS)-x86/$(LLVM_PREBUILTS_VERSION)/bin
+LLVM_PREBUILTS_VERSION ?= 3.8
+LLVM_PREBUILTS_BASE ?= prebuilts/clang/host
+LLVM_PREBUILTS_PATH := $(LLVM_PREBUILTS_BASE)/$(BUILD_OS)-x86/$(LLVM_PREBUILTS_VERSION)/bin
 LLVM_RTLIB_PATH := $(LLVM_PREBUILTS_PATH)/../lib/clang/$(LLVM_PREBUILTS_VERSION)/lib/linux/
 
 CLANG := $(LLVM_PREBUILTS_PATH)/clang$(BUILD_EXECUTABLE_SUFFIX)
diff --git a/core/definitions.mk b/core/definitions.mk
index 0089865..e6f4a04 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -530,7 +530,7 @@
         $(error $(LOCAL_PATH): Name not defined in call to generated-sources-dir-for)) \
     $(eval _idfPrefix := $(if $(strip $(3)),HOST,TARGET)) \
     $(if $(filter $(_idfPrefix)-$(_idfClass),$(COMMON_MODULE_CLASSES))$(4), \
-        $(eval _idfIntBase := $($(_idfPrefix)_OUT_GEN_COMMON)) \
+        $(eval _idfIntBase := $($(_idfPrefix)_OUT_COMMON_GEN)) \
       , \
         $(eval _idfIntBase := $($(_idfPrefix)_OUT_GEN)) \
      ) \
@@ -1760,7 +1760,8 @@
 endef
 
 # For a list of jar files, unzip them to a specified directory,
-# but make sure that no META-INF files come along for the ride.
+# but make sure that no META-INF files come along for the ride,
+# unless PRIVATE_DONT_DELETE_JAR_META_INF is set.
 #
 # $(1): files to unzip
 # $(2): destination directory
@@ -1772,8 +1773,8 @@
       exit 1; \
     fi; \
     unzip -qo $$f -d $(2); \
-  done \
-  $(if $(PRIVATE_DONT_DELETE_JAR_META_INF),,;rm -rf $(2)/META-INF)
+  done
+  $(if $(PRIVATE_DONT_DELETE_JAR_META_INF),,$(hide) rm -rf $(2)/META-INF)
 endef
 
 # Call jack
@@ -1875,7 +1876,7 @@
     $(hide) mkdir -p $@.res.tmp
     $(hide) $(call create-empty-package-at,$@.res.tmp.zip)
     $(hide) $(call add-java-resources-to,$@.res.tmp.zip)
-    $(hide) $(call unzip-jar-files,$@.res.tmp.zip,$@.res.tmp)
+    $(hide) unzip -qo $@.res.tmp.zip -d $@.res.tmp
     $(hide) rm $@.res.tmp.zip)
 $(hide) if [ -s $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq ] ; then \
     export tmpEcjArg="@$(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq"; \
@@ -1915,7 +1916,7 @@
 	$(hide) mkdir -p $(dir $@)
 	$(JILL) $(PRIVATE_JILL_FLAGS) --output $@.tmpjill.jack $<
 	$(hide) mkdir -p $@.tmpjill.res
-	$(hide) $(call unzip-jar-files,$<,$@.tmpjill.res)
+	$(hide) unzip -qo $< -d $@.tmpjill.res
 	$(hide) find $@.tmpjill.res -iname "*.class" -delete
 	$(hide) $(call call-jack) \
         -D jack.import.resource.policy=keep-first \
diff --git a/core/droiddoc.mk b/core/droiddoc.mk
index 74f7d8b..1e9de1c 100644
--- a/core/droiddoc.mk
+++ b/core/droiddoc.mk
@@ -171,7 +171,7 @@
 		javadoc \
                 -encoding UTF-8 \
                 \@$(PRIVATE_SRC_LIST_FILE) \
-                -J-Xmx1280m \
+                -J-Xmx1600m \
                 -XDignore.symbol.file \
                 $(PRIVATE_PROFILING_OPTIONS) \
                 -quiet \
diff --git a/core/goma.mk b/core/goma.mk
index ddd7d80..01a1d81 100644
--- a/core/goma.mk
+++ b/core/goma.mk
@@ -56,7 +56,11 @@
   # gomacc can start goma client's daemon process automatically, but
   # it is safer and faster to start up it beforehand. We run this as a
   # background process so this won't slow down the build.
-  $(shell $(goma_ctl) ensure_start &> /dev/null &)
+  # We use "ensure_start" command when the compiler_proxy is already
+  # running and uses GOMA_HERMETIC=error flag. The compiler_proxy will
+  # restart otherwise.
+  # TODO(hamaji): Remove this condition after http://b/25676777 is fixed.
+  $(shell ( if ( curl http://localhost:$$($(GOMA_CC) port)/flagz | grep GOMA_HERMETIC=error ); then cmd=ensure_start; else cmd=restart; fi; GOMA_HERMETIC=error $(goma_ctl) $${cmd} ) &> /dev/null &)
 
   goma_ctl :=
   goma_dir :=
diff --git a/core/main.mk b/core/main.mk
index 2bd3163..d9ff68e 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -113,6 +113,11 @@
 $(shell rm -f $(OUT_DIR)/ninja_build)
 endif
 
+# With these files findleaves.py won't be unnecessarily slower even if
+# there is a user creates a copy of $(OUT_DIR).
+$(shell echo '# This file prevents findleaves.py from traversing this directory further' > $(OUT_DIR)/Android.mk)
+$(shell echo '# This file prevents findleaves.py from traversing this directory further' > $(OUT_DIR)/CleanSpec.mk)
+
 # Write the build number to a file so it can be read back in
 # without changing the command line every time.  Avoids rebuilds
 # when using ninja.
diff --git a/core/ninja.mk b/core/ninja.mk
index 2e8f3c8..862ed80 100644
--- a/core/ninja.mk
+++ b/core/ninja.mk
@@ -1,5 +1,6 @@
 KATI ?= $(HOST_OUT_EXECUTABLES)/ckati
 MAKEPARALLEL ?= $(HOST_OUT_EXECUTABLES)/makeparallel
+NINJA ?= prebuilts/ninja/$(HOST_PREBUILT_TAG)/ninja
 
 KATI_OUTPUT_PATTERNS := $(OUT_DIR)/build%.ninja $(OUT_DIR)/ninja%.sh
 
@@ -89,7 +90,7 @@
 endif
 
 KATI_BUILD_NINJA := $(OUT_DIR)/build$(KATI_NINJA_SUFFIX).ninja
-KATI_NINJA_SH := $(OUT_DIR)/ninja$(KATI_NINJA_SUFFIX).sh
+KATI_ENV_SH := $(OUT_DIR)/env$(KATI_NINJA_SUFFIX).sh
 
 # Write out a file mapping checksum to the real suffix.
 ifneq ($(my_checksum_suffix),)
@@ -123,7 +124,7 @@
 .PHONY: ninja_wrapper
 ninja_wrapper: $(KATI_BUILD_NINJA) $(MAKEPARALLEL)
 	@echo Starting build with ninja
-	+$(hide) PATH=prebuilts/ninja/$(HOST_PREBUILT_TAG)/:$$PATH NINJA_STATUS="$(NINJA_STATUS)" $(NINJA_MAKEPARALLEL) $(KATI_NINJA_SH) $(NINJA_GOALS) -C $(TOP) $(NINJA_ARGS)
+	+$(hide) export NINJA_STATUS="$(NINJA_STATUS)" && source $(KATI_ENV_SH) && $(NINJA_MAKEPARALLEL) $(NINJA) $(NINJA_GOALS) -C $(TOP) -f $(KATI_BUILD_NINJA) $(NINJA_ARGS)
 
 KATI_FIND_EMULATOR := --use_find_emulator
 ifeq ($(KATI_EMULATE_FIND),false)
diff --git a/core/prebuilt_internal.mk b/core/prebuilt_internal.mk
index 3af7101..da76eb9 100644
--- a/core/prebuilt_internal.mk
+++ b/core/prebuilt_internal.mk
@@ -275,8 +275,13 @@
 $(built_module) : $(my_prebuilt_src_file)
 	$(transform-prebuilt-to-target-strip-comments)
 else
+ifneq ($(LOCAL_ACP_UNAVAILABLE),true)
 $(built_module) : $(my_prebuilt_src_file) | $(ACP)
 	$(transform-prebuilt-to-target)
+else
+$(built_module) : $(my_prebuilt_src_file)
+	$(copy-file-to-target-with-cp)
+endif
 endif
 endif # LOCAL_MODULE_CLASS != APPS
 
diff --git a/envsetup.sh b/envsetup.sh
index 45f70b3..f266f1a 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1413,11 +1413,7 @@
     \cd $T/$pathname
 }
 
-# Force JAVA_HOME to point to java 1.7 if it isn't already set.
-#
-# Note that the MacOS path for java 1.7 includes a minor revision number (sigh).
-# For some reason, installing the JDK doesn't make it show up in the
-# JavaVM.framework/Versions/1.7/ folder.
+# Force JAVA_HOME to point to java 1.7/1.8 if it isn't already set.
 function set_java_home() {
     # Clear the existing JAVA_HOME value if we set it ourselves, so that
     # we can reset it later, depending on the version of java the build
@@ -1430,14 +1426,25 @@
     fi
 
     if [ ! "$JAVA_HOME" ]; then
-      case `uname -s` in
-          Darwin)
-              export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
-              ;;
-          *)
-              export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
-              ;;
-      esac
+      if [ ! "$EXPERIMENTAL_USE_JAVA8" ]; then
+        case `uname -s` in
+            Darwin)
+                export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
+                ;;
+            *)
+                export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
+                ;;
+        esac
+      else
+        case `uname -s` in
+            Darwin)
+                export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
+                ;;
+            *)
+                export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
+                ;;
+        esac
+      fi
 
       # Keep track of the fact that we set JAVA_HOME ourselves, so that
       # we can change it on the next envsetup.sh, if required.
diff --git a/target/board/generic_mips/BoardConfig.mk b/target/board/generic_mips/BoardConfig.mk
index 76a2ef4..50924f8 100644
--- a/target/board/generic_mips/BoardConfig.mk
+++ b/target/board/generic_mips/BoardConfig.mk
@@ -61,3 +61,5 @@
 TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true
 
 BOARD_SEPOLICY_DIRS += build/target/board/generic/sepolicy
+
+USE_CLANG_PLATFORM_BUILD := true
diff --git a/target/board/generic_mips64/BoardConfig.mk b/target/board/generic_mips64/BoardConfig.mk
index 8e8a68b..d5430cd 100644
--- a/target/board/generic_mips64/BoardConfig.mk
+++ b/target/board/generic_mips64/BoardConfig.mk
@@ -75,3 +75,5 @@
 BOARD_SEPOLICY_DIRS += build/target/board/generic/sepolicy
 
 DEX_PREOPT_DEFAULT := nostripping
+
+USE_CLANG_PLATFORM_BUILD := true
diff --git a/target/product/aosp_arm.mk b/target/product/aosp_arm.mk
index 86b715c..781cae6 100644
--- a/target/product/aosp_arm.mk
+++ b/target/product/aosp_arm.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-$(call inherit-product, $(SRC_TARGET_DIR)/product/full.mk)
+include $(SRC_TARGET_DIR)/product/full.mk
 
 PRODUCT_NAME := aosp_arm
diff --git a/target/product/aosp_mips.mk b/target/product/aosp_mips.mk
index ceeb433..a76b93a 100644
--- a/target/product/aosp_mips.mk
+++ b/target/product/aosp_mips.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-$(call inherit-product, $(SRC_TARGET_DIR)/product/full_mips.mk)
+include $(SRC_TARGET_DIR)/product/full_mips.mk
 
 PRODUCT_NAME := aosp_mips
diff --git a/target/product/aosp_x86.mk b/target/product/aosp_x86.mk
index 3e9b018..cba43c4 100644
--- a/target/product/aosp_x86.mk
+++ b/target/product/aosp_x86.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-$(call inherit-product, $(SRC_TARGET_DIR)/product/full_x86.mk)
+include $(SRC_TARGET_DIR)/product/full_x86.mk
 
 PRODUCT_NAME := aosp_x86
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index b6cee63..7cb9072 100755
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -319,6 +319,23 @@
   banner("cache")
   AddCache(output_zip)
 
+  # For devices using A/B update, copy over images from RADIO/ to IMAGES/ and
+  # make sure we have all the needed images ready under IMAGES/.
+  ab_partitions = os.path.join(OPTIONS.input_tmp, "META", "ab_partitions.txt")
+  if os.path.exists(ab_partitions):
+    with open(ab_partitions, 'r') as f:
+      lines = f.readlines()
+    for line in lines:
+      img_name = line.strip() + ".img"
+      img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
+      if os.path.exists(img_radio_path):
+        common.ZipWrite(output_zip, img_radio_path,
+                        os.path.join("IMAGES", img_name))
+
+      # Zip spec says: All slashes MUST be forward slashes.
+      img_path = 'IMAGES/' + img_name
+      assert img_path in output_zip.namelist(), "cannot find " + img_name
+
   common.ZipClose(output_zip)
 
 def main(argv):
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 872dd4c..2c8bbc6 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -211,8 +211,11 @@
   makeint("boot_size")
   makeint("fstab_version")
 
-  d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
-                                 d.get("system_root_image", False))
+  if d.get("no_recovery", False) == "true":
+    d["fstab"] = None
+  else:
+    d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
+                                   d.get("system_root_image", False))
   d["build.prop"] = LoadBuildProp(read_helper)
   return d
 
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
index 3ddab11..740a343 100644
--- a/tools/signapk/SignApk.java
+++ b/tools/signapk/SignApk.java
@@ -135,7 +135,6 @@
 
     /** Returns the expected signature algorithm for this key type. */
     private static String getSignatureAlgorithm(X509Certificate cert) {
-        String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
         String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
         if ("RSA".equalsIgnoreCase(keyType)) {
             if (getDigestAlgorithm(cert) == USE_SHA256) {
@@ -246,8 +245,11 @@
              * Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
              * OID and use that to construct a KeyFactory.
              */
-            ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
-            PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
+            PrivateKeyInfo pki;
+            try (ASN1InputStream bIn =
+                    new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()))) {
+                pki = PrivateKeyInfo.getInstance(bIn.readObject());
+            }
             String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
 
             return KeyFactory.getInstance(algOid).generatePrivate(spec);
@@ -461,9 +463,10 @@
         gen.addCertificates(certs);
         CMSSignedData sigData = gen.generate(data, false);
 
-        ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
-        DEROutputStream dos = new DEROutputStream(out);
-        dos.writeObject(asn1.readObject());
+        try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {
+            DEROutputStream dos = new DEROutputStream(out);
+            dos.writeObject(asn1.readObject());
+        }
     }
 
     /**
@@ -616,7 +619,6 @@
         private File publicKeyFile;
         private X509Certificate publicKey;
         private PrivateKey privateKey;
-        private String outputFile;
         private OutputStream outputStream;
         private final ASN1ObjectIdentifier type;
         private WholeFileSignerOutputStream signer;
@@ -636,14 +638,17 @@
          * This should actually return byte[] or something similar, but nothing
          * actually checks it currently.
          */
+        @Override
         public Object getContent() {
             return this;
         }
 
+        @Override
         public ASN1ObjectIdentifier getContentType() {
             return type;
         }
 
+        @Override
         public void write(OutputStream out) throws IOException {
             try {
                 signer = new WholeFileSignerOutputStream(out, outputStream);
@@ -658,7 +663,7 @@
                 copyFiles(manifest, inputJar, outputJar, timestamp, 0);
                 addOtacert(outputJar, publicKeyFile, timestamp, manifest, hash);
 
-                signFile(manifest, inputJar,
+                signFile(manifest,
                          new X509Certificate[]{ publicKey },
                          new PrivateKey[]{ privateKey },
                          outputJar);
@@ -753,7 +758,7 @@
         temp.writeTo(outputStream);
     }
 
-    private static void signFile(Manifest manifest, JarFile inputJar,
+    private static void signFile(Manifest manifest,
                                  X509Certificate[] publicKey, PrivateKey[] privateKey,
                                  JarOutputStream outputJar)
         throws Exception {
@@ -860,7 +865,6 @@
 
         boolean signWholeFile = false;
         String providerClass = null;
-        String providerArg = null;
         int alignment = 4;
 
         int argstart = 0;
@@ -944,7 +948,7 @@
 
                 Manifest manifest = addDigestsToManifest(inputJar, hashes);
                 copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
-                signFile(manifest, inputJar, publicKey, privateKey, outputJar);
+                signFile(manifest, publicKey, privateKey, outputJar);
                 outputJar.close();
             }
         } catch (Exception e) {
diff --git a/tools/target_files_diff.py b/tools/target_files_diff.py
index 010ea84..405c42b 100755
--- a/tools/target_files_diff.py
+++ b/tools/target_files_diff.py
@@ -26,6 +26,7 @@
 import os
 import re
 import subprocess
+import sys
 import tempfile
 
 def ignore(name):
@@ -94,14 +95,16 @@
 
 def trim_install_recovery(original, new):
   """
-  Rewrite the install-recovery script to remove the hash of the recovery partition.
+  Rewrite the install-recovery script to remove the hash of the recovery
+  partition.
   """
   for line in original:
     new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
 
 def sort_file(original, new):
   """
-  Sort the file. Some OTA metadata files are not in a deterministic order currently.
+  Sort the file. Some OTA metadata files are not in a deterministic order
+  currently.
   """
   lines = original.readlines()
   lines.sort()
@@ -126,7 +129,8 @@
 @contextlib.contextmanager
 def preprocess(name, filename):
   """
-  Optionally rewrite files before diffing them, to remove known-variable information.
+  Optionally rewrite files before diffing them, to remove known-variable
+  information.
   """
   if name in REWRITE_RULES:
     with tempfile.NamedTemporaryFile() as newfp:
@@ -137,24 +141,25 @@
   else:
     yield filename
 
-def diff(name, file1, file2):
+def diff(name, file1, file2, out_file):
   """
   Diff a file pair with diff, running preprocess() on the arguments first.
   """
   with preprocess(name, file1) as f1:
     with preprocess(name, file2) as f2:
-      proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-      (stdout, ignore) = proc.communicate()
+      proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
+                              stderr=subprocess.STDOUT)
+      (stdout, _) = proc.communicate()
       if proc.returncode == 0:
         return
       stdout = stdout.strip()
       if stdout == 'Binary files %s and %s differ' % (f1, f2):
-        print("%s: Binary files differ" % name)
+        print("%s: Binary files differ" % name, file=out_file)
       else:
         for line in stdout.strip().split('\n'):
-          print("%s: %s" % (name, line))
+          print("%s: %s" % (name, line), file=out_file)
 
-def recursiveDiff(prefix, dir1, dir2):
+def recursiveDiff(prefix, dir1, dir2, out_file):
   """
   Recursively diff two directories, checking metadata then calling diff()
   """
@@ -175,31 +180,34 @@
           link1 = os.readlink(name1)
           link2 = os.readlink(name2)
           if link1 != link2:
-            print("%s: Symlinks differ: %s vs %s" % (name, link1, link2))
+            print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
+                  file=out_file)
         else:
-          print("%s: File types differ, skipping compare" % name)
+          print("%s: File types differ, skipping compare" % name,
+                file=out_file)
         continue
 
       stat1 = os.stat(name1)
       stat2 = os.stat(name2)
-      type1 = stat1.st_mode & ~0777
-      type2 = stat2.st_mode & ~0777
+      type1 = stat1.st_mode & ~0o777
+      type2 = stat2.st_mode & ~0o777
 
       if type1 != type2:
-        print("%s: File types differ, skipping compare" % name)
+        print("%s: File types differ, skipping compare" % name, file=out_file)
         continue
 
       if stat1.st_mode != stat2.st_mode:
-        print("%s: Modes differ: %o vs %o" % (name, stat1.st_mode, stat2.st_mode))
+        print("%s: Modes differ: %o vs %o" %
+            (name, stat1.st_mode, stat2.st_mode), file=out_file)
 
       if os.path.isdir(name1):
-        recursiveDiff(name, name1, name2)
+        recursiveDiff(name, name1, name2, out_file)
       elif os.path.isfile(name1):
-        diff(name, name1, name2)
+        diff(name, name1, name2, out_file)
       else:
-        print("%s: Unknown file type, skipping compare" % name)
+        print("%s: Unknown file type, skipping compare" % name, file=out_file)
     else:
-      print("%s: Only in base package" % name)
+      print("%s: Only in base package" % name, file=out_file)
 
   for entry in list2:
     name = os.path.join(prefix, entry)
@@ -210,15 +218,25 @@
       continue
 
     if entry not in list1:
-      print("%s: Only in new package" % name)
+      print("%s: Only in new package" % name, file=out_file)
 
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('dir1', help='The base target files package (extracted)')
   parser.add_argument('dir2', help='The new target files package (extracted)')
+  parser.add_argument('--output',
+      help='The output file, otherwise it prints to stdout')
   args = parser.parse_args()
 
-  recursiveDiff('', args.dir1, args.dir2)
+  if args.output:
+    out_file = open(args.output, 'w')
+  else:
+    out_file = sys.stdout
+
+  recursiveDiff('', args.dir1, args.dir2, out_file)
+
+  if args.output:
+    out_file.close()
 
 if __name__ == '__main__':
   main()