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