234 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env python
 | 
						|
#
 | 
						|
# Copyright (C) 2009 The Android Open Source Project
 | 
						|
#
 | 
						|
# 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.
 | 
						|
#
 | 
						|
 | 
						|
#
 | 
						|
# Finds differences between two target files packages
 | 
						|
#
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
import argparse
 | 
						|
import contextlib
 | 
						|
import os
 | 
						|
import re
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
import tempfile
 | 
						|
 | 
						|
def ignore(name):
 | 
						|
  """
 | 
						|
  Files to ignore when diffing
 | 
						|
 | 
						|
  These are packages that we're already diffing elsewhere,
 | 
						|
  or files that we expect to be different for every build,
 | 
						|
  or known problems.
 | 
						|
  """
 | 
						|
 | 
						|
  # We're looking at the files that make the images, so no need to search them
 | 
						|
  if name in ['IMAGES']:
 | 
						|
    return True
 | 
						|
  # These are packages of the recovery partition, which we're already diffing
 | 
						|
  if name in ['SYSTEM/etc/recovery-resource.dat',
 | 
						|
              'SYSTEM/recovery-from-boot.p']:
 | 
						|
    return True
 | 
						|
 | 
						|
  # These files are just the BUILD_NUMBER, and will always be different
 | 
						|
  if name in ['BOOT/RAMDISK/selinux_version',
 | 
						|
              'RECOVERY/RAMDISK/selinux_version']:
 | 
						|
    return True
 | 
						|
 | 
						|
  return False
 | 
						|
 | 
						|
 | 
						|
def rewrite_build_property(original, new):
 | 
						|
  """
 | 
						|
  Rewrite property files to remove values known to change for every build
 | 
						|
  """
 | 
						|
 | 
						|
  skipped = ['ro.bootimage.build.date=',
 | 
						|
             'ro.bootimage.build.date.utc=',
 | 
						|
             'ro.bootimage.build.fingerprint=',
 | 
						|
             'ro.build.id=',
 | 
						|
             'ro.build.display.id=',
 | 
						|
             'ro.build.version.incremental=',
 | 
						|
             'ro.build.date=',
 | 
						|
             'ro.build.date.utc=',
 | 
						|
             'ro.build.host=',
 | 
						|
             'ro.build.user=',
 | 
						|
             'ro.build.description=',
 | 
						|
             'ro.build.fingerprint=',
 | 
						|
             'ro.vendor.build.date=',
 | 
						|
             'ro.vendor.build.date.utc=',
 | 
						|
             'ro.vendor.build.fingerprint=']
 | 
						|
 | 
						|
  for line in original:
 | 
						|
    skip = False
 | 
						|
    for s in skipped:
 | 
						|
      if line.startswith(s):
 | 
						|
        skip = True
 | 
						|
        break
 | 
						|
    if not skip:
 | 
						|
      new.write(line.encode())
 | 
						|
 | 
						|
 | 
						|
def trim_install_recovery(original, new):
 | 
						|
  """
 | 
						|
  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).encode())
 | 
						|
 | 
						|
def sort_file(original, new):
 | 
						|
  """
 | 
						|
  Sort the file. Some OTA metadata files are not in a deterministic order
 | 
						|
  currently.
 | 
						|
  """
 | 
						|
  lines = original.readlines()
 | 
						|
  lines.sort()
 | 
						|
  for line in lines:
 | 
						|
    new.write(line.encode())
 | 
						|
 | 
						|
# Map files to the functions that will modify them for diffing
 | 
						|
REWRITE_RULES = {
 | 
						|
    'BOOT/RAMDISK/default.prop': rewrite_build_property,
 | 
						|
    'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
 | 
						|
    'SYSTEM/build.prop': rewrite_build_property,
 | 
						|
    'VENDOR/build.prop': rewrite_build_property,
 | 
						|
 | 
						|
    'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
 | 
						|
 | 
						|
    'META/boot_filesystem_config.txt': sort_file,
 | 
						|
    'META/filesystem_config.txt': sort_file,
 | 
						|
    'META/recovery_filesystem_config.txt': sort_file,
 | 
						|
    'META/vendor_filesystem_config.txt': sort_file,
 | 
						|
}
 | 
						|
 | 
						|
@contextlib.contextmanager
 | 
						|
def preprocess(name, filename):
 | 
						|
  """
 | 
						|
  Optionally rewrite files before diffing them, to remove known-variable
 | 
						|
  information.
 | 
						|
  """
 | 
						|
  if name in REWRITE_RULES:
 | 
						|
    with tempfile.NamedTemporaryFile() as newfp:
 | 
						|
      with open(filename, 'r') as oldfp:
 | 
						|
        REWRITE_RULES[name](oldfp, newfp)
 | 
						|
      newfp.flush()
 | 
						|
      yield newfp.name
 | 
						|
  else:
 | 
						|
    yield filename
 | 
						|
 | 
						|
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, _) = 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, file=out_file)
 | 
						|
      else:
 | 
						|
        for line in stdout.strip().split(b'\n'):
 | 
						|
          print("%s: %s" % (name, line), file=out_file)
 | 
						|
 | 
						|
def recursiveDiff(prefix, dir1, dir2, out_file):
 | 
						|
  """
 | 
						|
  Recursively diff two directories, checking metadata then calling diff()
 | 
						|
  """
 | 
						|
  list1 = sorted(os.listdir(dir1))
 | 
						|
  list2 = sorted(os.listdir(dir2))
 | 
						|
 | 
						|
  for entry in list1:
 | 
						|
    name = os.path.join(prefix, entry)
 | 
						|
    name1 = os.path.join(dir1, entry)
 | 
						|
    name2 = os.path.join(dir2, entry)
 | 
						|
 | 
						|
    if ignore(name):
 | 
						|
      continue
 | 
						|
 | 
						|
    if entry in list2:
 | 
						|
      if os.path.islink(name1) and os.path.islink(name2):
 | 
						|
        link1 = os.readlink(name1)
 | 
						|
        link2 = os.readlink(name2)
 | 
						|
        if link1 != link2:
 | 
						|
          print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
 | 
						|
                file=out_file)
 | 
						|
        continue
 | 
						|
      elif os.path.islink(name1) or os.path.islink(name2):
 | 
						|
        print("%s: File types differ, skipping compare" % name, file=out_file)
 | 
						|
        continue
 | 
						|
 | 
						|
      stat1 = os.stat(name1)
 | 
						|
      stat2 = os.stat(name2)
 | 
						|
      type1 = stat1.st_mode & ~0o777
 | 
						|
      type2 = stat2.st_mode & ~0o777
 | 
						|
 | 
						|
      if type1 != type2:
 | 
						|
        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), file=out_file)
 | 
						|
 | 
						|
      if os.path.isdir(name1):
 | 
						|
        recursiveDiff(name, name1, name2, out_file)
 | 
						|
      elif os.path.isfile(name1):
 | 
						|
        diff(name, name1, name2, out_file)
 | 
						|
      else:
 | 
						|
        print("%s: Unknown file type, skipping compare" % name, file=out_file)
 | 
						|
    else:
 | 
						|
      print("%s: Only in base package" % name, file=out_file)
 | 
						|
 | 
						|
  for entry in list2:
 | 
						|
    name = os.path.join(prefix, entry)
 | 
						|
    name1 = os.path.join(dir1, entry)
 | 
						|
    name2 = os.path.join(dir2, entry)
 | 
						|
 | 
						|
    if ignore(name):
 | 
						|
      continue
 | 
						|
 | 
						|
    if entry not in list1:
 | 
						|
      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()
 | 
						|
 | 
						|
  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()
 |