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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « components/cronet/tools/api_static_checks_unittest.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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)
« 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