OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """update_api.py - Update committed Cronet API.""" |
| 7 |
| 8 import argparse |
| 9 import filecmp |
| 10 import fileinput |
| 11 import os |
| 12 import re |
| 13 import shutil |
| 14 import sys |
| 15 import tempfile |
| 16 |
| 17 # Filename of dump of current API. |
| 18 API_FILENAME = os.path.abspath(os.path.join( |
| 19 os.path.dirname(__file__), '..', 'android', 'api.txt')) |
| 20 # Filename of file containing API version number. |
| 21 API_VERSION_FILENAME = os.path.abspath(os.path.join( |
| 22 os.path.dirname(__file__), '..', 'android', 'api_version.txt')) |
| 23 |
| 24 # Regular expression that catches the beginning of lines that declare classes. |
| 25 # The first group returned by a match is the class name. |
| 26 CLASS_RE = re.compile(r'.*class ([^ ]*) .*\{') |
| 27 |
| 28 # Regular expression that matches a string containing an unnamed class name, |
| 29 # for example 'Foo$1'. |
| 30 UNNAMED_CLASS_RE = re.compile(r'.*\$[0-9]') |
| 31 |
| 32 |
| 33 def generate_api(api_jar, output_filename): |
| 34 # Dumps the API in |api_jar| into |outpuf_filename|. |
| 35 |
| 36 with open(output_filename, 'w') as output_file: |
| 37 output_file.write( |
| 38 'DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT\n\n') |
| 39 |
| 40 # Extract API class files from api_jar. |
| 41 temp_dir = tempfile.mkdtemp() |
| 42 old_cwd = os.getcwd() |
| 43 api_jar_path = os.path.abspath(api_jar) |
| 44 os.chdir(temp_dir) |
| 45 if os.system('jar xf %s' % api_jar_path): |
| 46 print 'ERROR: jar failed on ' + api_jar |
| 47 return False |
| 48 os.chdir(old_cwd) |
| 49 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) |
| 50 |
| 51 # Collect names of all API class files |
| 52 api_class_files = [] |
| 53 for root, _, filenames in os.walk(temp_dir): |
| 54 api_class_files += [os.path.join(root, f) for f in filenames] |
| 55 api_class_files.sort() |
| 56 |
| 57 # Dump API class files into |output_filename| |
| 58 javap_cmd = ('javap -protected %s >> %s' % (' '.join(api_class_files), |
| 59 output_filename)).replace('$', '\\$') |
| 60 if os.system(javap_cmd): |
| 61 print 'ERROR: javap command failed: ' + javap_cmd |
| 62 return False |
| 63 shutil.rmtree(temp_dir) |
| 64 |
| 65 # Strip out pieces we don't need to compare. |
| 66 output_file = fileinput.FileInput(output_filename, inplace=True) |
| 67 skip_to_next_class = False |
| 68 for line in output_file: |
| 69 # Skip 'Compiled from ' lines as they're not part of the API. |
| 70 if line.startswith('Compiled from "'): |
| 71 continue |
| 72 if CLASS_RE.match(line): |
| 73 skip_to_next_class = ( |
| 74 # Skip internal classes, they aren't exposed. |
| 75 UNNAMED_CLASS_RE.match(line) or |
| 76 # Skip experimental classes, they can be modified. |
| 77 'Experimental' in line |
| 78 ) |
| 79 if skip_to_next_class: |
| 80 skip_to_next_class = line != '}' |
| 81 continue |
| 82 sys.stdout.write(line) |
| 83 output_file.close() |
| 84 return True |
| 85 |
| 86 |
| 87 def check_up_to_date(api_jar): |
| 88 # Returns True if API_FILENAME matches the API exposed by |api_jar|. |
| 89 |
| 90 [_, temp_filename] = tempfile.mkstemp() |
| 91 if not generate_api(api_jar, temp_filename): |
| 92 return False |
| 93 ret = filecmp.cmp(API_FILENAME, temp_filename) |
| 94 os.remove(temp_filename) |
| 95 return ret |
| 96 |
| 97 |
| 98 def check_api_update(old_api, new_api): |
| 99 # Enforce that lines are only added when updating API. |
| 100 with open(old_api, 'r') as old_api_file, open(new_api, 'r') as new_api_file: |
| 101 for old_line in old_api_file: |
| 102 while True: |
| 103 new_line = new_api_file.readline() |
| 104 if new_line == old_line: |
| 105 break |
| 106 if not new_line: |
| 107 print 'ERROR: This API was modified or removed:' |
| 108 print ' ' + old_line |
| 109 print ' Cronet API methods and classes cannot be modified.' |
| 110 return False |
| 111 return True |
| 112 |
| 113 |
| 114 def main(args): |
| 115 parser = argparse.ArgumentParser(description='Update Cronet api.txt.') |
| 116 parser.add_argument('--api_jar', |
| 117 help='Path to API jar (i.e. cronet_api.jar)', |
| 118 required=True, |
| 119 metavar='path/to/cronet_api.jar') |
| 120 opts = parser.parse_args(args) |
| 121 |
| 122 if check_up_to_date(opts.api_jar): |
| 123 return True |
| 124 |
| 125 [_, temp_filename] = tempfile.mkstemp() |
| 126 if (generate_api(opts.api_jar, temp_filename) and |
| 127 check_api_update(API_FILENAME, temp_filename)): |
| 128 # Update API version number to new version number |
| 129 with open(API_VERSION_FILENAME,'r+') as f: |
| 130 version = int(f.read()) |
| 131 f.seek(0) |
| 132 f.write(str(version + 1)) |
| 133 # Update API file to new API |
| 134 shutil.move(temp_filename, API_FILENAME) |
| 135 return True |
| 136 os.remove(temp_filename) |
| 137 return False |
| 138 |
| 139 |
| 140 if __name__ == '__main__': |
| 141 sys.exit(0 if main(sys.argv[1:]) else -1) |
OLD | NEW |