Index: components/cronet/tools/update_api.py |
diff --git a/components/cronet/tools/update_api.py b/components/cronet/tools/update_api.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..fe5efb3f90d03f4934a4636475d6a52e60360958 |
--- /dev/null |
+++ b/components/cronet/tools/update_api.py |
@@ -0,0 +1,141 @@ |
+#!/usr/bin/python |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""update_api.py - Update committed Cronet API.""" |
+ |
+import argparse |
+import filecmp |
+import fileinput |
+import os |
+import re |
+import shutil |
+import sys |
+import tempfile |
+ |
+# Filename of dump of current API. |
+API_FILENAME = os.path.abspath(os.path.join( |
+ os.path.dirname(__file__), '..', 'android', 'api.txt')) |
+# Filename of file containing API version number. |
+API_VERSION_FILENAME = os.path.abspath(os.path.join( |
+ os.path.dirname(__file__), '..', 'android', 'api_version.txt')) |
+ |
+# Regular expression that catches the beginning of lines that declare classes. |
+# The first group returned by a match is the class name. |
+CLASS_RE = re.compile(r'.*class ([^ ]*) .*\{') |
+ |
+# Regular expression that matches a string containing an unnamed class name, |
+# for example 'Foo$1'. |
+UNNAMED_CLASS_RE = re.compile(r'.*\$[0-9]') |
+ |
+ |
+def generate_api(api_jar, output_filename): |
+ # Dumps the API in |api_jar| into |outpuf_filename|. |
+ |
+ with open(output_filename, 'w') as output_file: |
+ output_file.write( |
+ 'DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT\n\n') |
+ |
+ # Extract API class files from api_jar. |
+ temp_dir = tempfile.mkdtemp() |
+ old_cwd = os.getcwd() |
+ api_jar_path = os.path.abspath(api_jar) |
+ os.chdir(temp_dir) |
+ if os.system('jar xf %s' % api_jar_path): |
+ print 'ERROR: jar failed on ' + api_jar |
+ return False |
+ os.chdir(old_cwd) |
+ shutil.rmtree(os.path.join(temp_dir, 'META-INF')) |
+ |
+ # Collect names of all API class files |
+ api_class_files = [] |
+ for root, _, filenames in os.walk(temp_dir): |
+ api_class_files += [os.path.join(root, f) for f in filenames] |
+ api_class_files.sort() |
+ |
+ # Dump API class files into |output_filename| |
+ javap_cmd = ('javap -protected %s >> %s' % (' '.join(api_class_files), |
+ output_filename)).replace('$', '\\$') |
+ if os.system(javap_cmd): |
+ print 'ERROR: javap command failed: ' + javap_cmd |
+ return False |
+ shutil.rmtree(temp_dir) |
+ |
+ # Strip out pieces we don't need to compare. |
+ output_file = fileinput.FileInput(output_filename, inplace=True) |
+ skip_to_next_class = False |
+ for line in output_file: |
+ # Skip 'Compiled from ' lines as they're not part of the API. |
+ if line.startswith('Compiled from "'): |
+ continue |
+ if CLASS_RE.match(line): |
+ skip_to_next_class = ( |
+ # Skip internal classes, they aren't exposed. |
+ UNNAMED_CLASS_RE.match(line) or |
+ # Skip experimental classes, they can be modified. |
+ 'Experimental' in line |
+ ) |
+ if skip_to_next_class: |
+ skip_to_next_class = line != '}' |
+ continue |
+ sys.stdout.write(line) |
+ output_file.close() |
+ return True |
+ |
+ |
+def check_up_to_date(api_jar): |
+ # Returns True if API_FILENAME matches the API exposed by |api_jar|. |
+ |
+ [_, temp_filename] = tempfile.mkstemp() |
+ if not generate_api(api_jar, temp_filename): |
+ return False |
+ ret = filecmp.cmp(API_FILENAME, temp_filename) |
+ os.remove(temp_filename) |
+ return ret |
+ |
+ |
+def check_api_update(old_api, new_api): |
+ # Enforce that lines are only added when updating API. |
+ with open(old_api, 'r') as old_api_file, open(new_api, 'r') as new_api_file: |
+ for old_line in old_api_file: |
+ while True: |
+ new_line = new_api_file.readline() |
+ if new_line == old_line: |
+ break |
+ if not new_line: |
+ print 'ERROR: This API was modified or removed:' |
+ print ' ' + old_line |
+ print ' Cronet API methods and classes cannot be modified.' |
+ return False |
+ return True |
+ |
+ |
+def main(args): |
+ parser = argparse.ArgumentParser(description='Update Cronet api.txt.') |
+ parser.add_argument('--api_jar', |
+ help='Path to API jar (i.e. cronet_api.jar)', |
+ required=True, |
+ metavar='path/to/cronet_api.jar') |
+ opts = parser.parse_args(args) |
+ |
+ if check_up_to_date(opts.api_jar): |
+ return True |
+ |
+ [_, temp_filename] = tempfile.mkstemp() |
+ if (generate_api(opts.api_jar, temp_filename) and |
+ check_api_update(API_FILENAME, temp_filename)): |
+ # Update API version number to new version number |
+ with open(API_VERSION_FILENAME,'r+') as f: |
+ version = int(f.read()) |
+ f.seek(0) |
+ f.write(str(version + 1)) |
+ # Update API file to new API |
+ shutil.move(temp_filename, API_FILENAME) |
+ return True |
+ os.remove(temp_filename) |
+ return False |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(0 if main(sys.argv[1:]) else -1) |