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 |