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

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

Issue 2544043002: [Cronet] Enforce Cronet API never modified, only grown (Closed)
Patch Set: fix Created 4 years 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.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 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)
OLDNEW
« no previous file with comments | « components/cronet/tools/api_static_checks.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698