Chromium Code Reviews| 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..ad537105f6bc6d30785d544db8493ce0b4069551 |
| --- /dev/null |
| +++ b/components/cronet/tools/update_api.py |
| @@ -0,0 +1,138 @@ |
| +#!/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 |
| + |
| +REPOSITORY_ROOT = os.path.abspath(os.path.join( |
| + os.path.dirname(__file__), '..', '..', '..')) |
| + |
| +sys.path.append(os.path.join(REPOSITORY_ROOT, 'build/android/gyp/util')) |
| +import build_utils |
| + |
| +# 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 ([^ ]*) .*{') |
|
kapishnikov
2016/12/01 23:28:34
Backward slash needed before '{'
pauljensen
2016/12/16 19:17:03
Done.
|
| + |
| +# 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') |
|
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.
|
| + |
| + # Extract API class files from api_jar. |
| + temp_dir = tempfile.mkdtemp() |
| + jar_cmd = ['jar', 'xf', os.path.abspath(api_jar)] |
| + build_utils.CheckOutput(jar_cmd, cwd=temp_dir) |
| + shutil.rmtree(os.path.join(temp_dir, 'META-INF')) |
| + |
| + # Dump API class files into |output_filename| |
| + 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.
|
| + if not filenames: |
| + continue |
| + if os.system('javap -protected %s >> %s' % ( |
| + ' '.join(os.path.join(root, f) for f in filenames).replace('$', '\\$'), |
| + output_filename)): |
| + 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.
|
| + 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) |