github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/releasetools/target_files_diff.py (about)

     1  #!/usr/bin/env python
     2  #
     3  # Copyright (C) 2009 The Android Open Source Project
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #      http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  #
    17  
    18  #
    19  # Finds differences between two target files packages
    20  #
    21  
    22  from __future__ import print_function
    23  
    24  import argparse
    25  import contextlib
    26  import os
    27  import re
    28  import subprocess
    29  import sys
    30  import tempfile
    31  
    32  def ignore(name):
    33    """
    34    Files to ignore when diffing
    35  
    36    These are packages that we're already diffing elsewhere,
    37    or files that we expect to be different for every build,
    38    or known problems.
    39    """
    40  
    41    # We're looking at the files that make the images, so no need to search them
    42    if name in ['IMAGES']:
    43      return True
    44    # These are packages of the recovery partition, which we're already diffing
    45    if name in ['SYSTEM/etc/recovery-resource.dat',
    46                'SYSTEM/recovery-from-boot.p']:
    47      return True
    48  
    49    # These files are just the BUILD_NUMBER, and will always be different
    50    if name in ['BOOT/RAMDISK/selinux_version',
    51                'RECOVERY/RAMDISK/selinux_version']:
    52      return True
    53  
    54    return False
    55  
    56  
    57  def rewrite_build_property(original, new):
    58    """
    59    Rewrite property files to remove values known to change for every build
    60    """
    61  
    62    skipped = ['ro.bootimage.build.date=',
    63               'ro.bootimage.build.date.utc=',
    64               'ro.bootimage.build.fingerprint=',
    65               'ro.build.id=',
    66               'ro.build.display.id=',
    67               'ro.build.version.incremental=',
    68               'ro.build.date=',
    69               'ro.build.date.utc=',
    70               'ro.build.host=',
    71               'ro.build.user=',
    72               'ro.build.description=',
    73               'ro.build.fingerprint=',
    74               'ro.expect.recovery_id=',
    75               'ro.vendor.build.date=',
    76               'ro.vendor.build.date.utc=',
    77               'ro.vendor.build.fingerprint=']
    78  
    79    for line in original:
    80      skip = False
    81      for s in skipped:
    82        if line.startswith(s):
    83          skip = True
    84          break
    85      if not skip:
    86        new.write(line)
    87  
    88  
    89  def trim_install_recovery(original, new):
    90    """
    91    Rewrite the install-recovery script to remove the hash of the recovery
    92    partition.
    93    """
    94    for line in original:
    95      new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
    96  
    97  def sort_file(original, new):
    98    """
    99    Sort the file. Some OTA metadata files are not in a deterministic order
   100    currently.
   101    """
   102    lines = original.readlines()
   103    lines.sort()
   104    for line in lines:
   105      new.write(line)
   106  
   107  # Map files to the functions that will modify them for diffing
   108  REWRITE_RULES = {
   109      'BOOT/RAMDISK/default.prop': rewrite_build_property,
   110      'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
   111      'SYSTEM/build.prop': rewrite_build_property,
   112      'VENDOR/build.prop': rewrite_build_property,
   113  
   114      'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
   115  
   116      'META/boot_filesystem_config.txt': sort_file,
   117      'META/filesystem_config.txt': sort_file,
   118      'META/recovery_filesystem_config.txt': sort_file,
   119      'META/vendor_filesystem_config.txt': sort_file,
   120  }
   121  
   122  @contextlib.contextmanager
   123  def preprocess(name, filename):
   124    """
   125    Optionally rewrite files before diffing them, to remove known-variable
   126    information.
   127    """
   128    if name in REWRITE_RULES:
   129      with tempfile.NamedTemporaryFile() as newfp:
   130        with open(filename, 'r') as oldfp:
   131          REWRITE_RULES[name](oldfp, newfp)
   132        newfp.flush()
   133        yield newfp.name
   134    else:
   135      yield filename
   136  
   137  def diff(name, file1, file2, out_file):
   138    """
   139    Diff a file pair with diff, running preprocess() on the arguments first.
   140    """
   141    with preprocess(name, file1) as f1:
   142      with preprocess(name, file2) as f2:
   143        proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
   144                                stderr=subprocess.STDOUT)
   145        (stdout, _) = proc.communicate()
   146        if proc.returncode == 0:
   147          return
   148        stdout = stdout.strip()
   149        if stdout == 'Binary files %s and %s differ' % (f1, f2):
   150          print("%s: Binary files differ" % name, file=out_file)
   151        else:
   152          for line in stdout.strip().split('\n'):
   153            print("%s: %s" % (name, line), file=out_file)
   154  
   155  def recursiveDiff(prefix, dir1, dir2, out_file):
   156    """
   157    Recursively diff two directories, checking metadata then calling diff()
   158    """
   159    list1 = sorted(os.listdir(dir1))
   160    list2 = sorted(os.listdir(dir2))
   161  
   162    for entry in list1:
   163      name = os.path.join(prefix, entry)
   164      name1 = os.path.join(dir1, entry)
   165      name2 = os.path.join(dir2, entry)
   166  
   167      if ignore(name):
   168        continue
   169  
   170      if entry in list2:
   171        if os.path.islink(name1) and os.path.islink(name2):
   172          link1 = os.readlink(name1)
   173          link2 = os.readlink(name2)
   174          if link1 != link2:
   175            print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
   176                  file=out_file)
   177          continue
   178        elif os.path.islink(name1) or os.path.islink(name2):
   179          print("%s: File types differ, skipping compare" % name, file=out_file)
   180          continue
   181  
   182        stat1 = os.stat(name1)
   183        stat2 = os.stat(name2)
   184        type1 = stat1.st_mode & ~0o777
   185        type2 = stat2.st_mode & ~0o777
   186  
   187        if type1 != type2:
   188          print("%s: File types differ, skipping compare" % name, file=out_file)
   189          continue
   190  
   191        if stat1.st_mode != stat2.st_mode:
   192          print("%s: Modes differ: %o vs %o" %
   193              (name, stat1.st_mode, stat2.st_mode), file=out_file)
   194  
   195        if os.path.isdir(name1):
   196          recursiveDiff(name, name1, name2, out_file)
   197        elif os.path.isfile(name1):
   198          diff(name, name1, name2, out_file)
   199        else:
   200          print("%s: Unknown file type, skipping compare" % name, file=out_file)
   201      else:
   202        print("%s: Only in base package" % name, file=out_file)
   203  
   204    for entry in list2:
   205      name = os.path.join(prefix, entry)
   206      name1 = os.path.join(dir1, entry)
   207      name2 = os.path.join(dir2, entry)
   208  
   209      if ignore(name):
   210        continue
   211  
   212      if entry not in list1:
   213        print("%s: Only in new package" % name, file=out_file)
   214  
   215  def main():
   216    parser = argparse.ArgumentParser()
   217    parser.add_argument('dir1', help='The base target files package (extracted)')
   218    parser.add_argument('dir2', help='The new target files package (extracted)')
   219    parser.add_argument('--output',
   220        help='The output file, otherwise it prints to stdout')
   221    args = parser.parse_args()
   222  
   223    if args.output:
   224      out_file = open(args.output, 'w')
   225    else:
   226      out_file = sys.stdout
   227  
   228    recursiveDiff('', args.dir1, args.dir2, out_file)
   229  
   230    if args.output:
   231      out_file.close()
   232  
   233  if __name__ == '__main__':
   234    main()