Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(232)

Side by Side Diff: components/cronet/tools/update_api.py

Issue 2544043002: [Cronet] Enforce Cronet API never modified, only grown (Closed)
Patch Set: remove absolute_import Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « components/cronet/tools/api_static_checks_unittest.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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)
OLDNEW
« no previous file with comments | « components/cronet/tools/api_static_checks_unittest.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698