Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / build / scripts / audit_runtime_enabled_features.py
blob93809406d301e651babbee0129672c29f59f5715
1 #!/usr/bin/env python
3 import requests
4 import re
5 from in_file import InFile
7 BRANCH_FORMAT = "https://src.chromium.org/blink/branches/chromium/%s/%s"
8 TRUNK_PATH = "Source/platform/RuntimeEnabledFeatures.in"
9 TRUNK_URL = "https://src.chromium.org/blink/trunk/%s" % TRUNK_PATH
12 def features_path(branch):
13 # RuntimeEnabledFeatures has only existed since April 2013:
14 if branch <= 1453:
15 return None
16 # Source/core/page/RuntimeEnabledFeatures.in existed by 1547
17 # but was in an old format without status= arguments.
18 if branch <= 1547:
19 return None
20 if branch <= 1650:
21 return "Source/core/page/RuntimeEnabledFeatures.in"
22 # Modern location:
23 return TRUNK_PATH
26 def parse_features_file(features_text):
27 valid_values = {
28 'status': ['stable', 'experimental', 'deprecated', 'test'],
30 defaults = {
31 'condition': None,
32 'depends_on': [],
33 'custom': False,
34 'status': None,
37 # FIXME: in_file.py manually calls str.strip so conver to str here.
38 features_lines = str(features_text).split("\n")
39 return InFile(features_lines, defaults, valid_values)
42 def stable_features(in_file):
43 return [feature['name'] for feature in in_file.name_dictionaries if feature['status'] == 'stable']
46 def branch_from_version(version_string):
47 # Format: 31.0.1650.63, the second digit was only ever used for M4
48 # no clue what it's actually intended for.
49 version_regexp = r"(?P<major>\d+)\.\d+\.(?P<branch>\d+)\.(?P<minor>\d+)"
50 match = re.match(version_regexp, version_string)
51 # if match == None, we'll blow up, so at least provide some debugging information:
52 if not match:
53 print version_string
54 return int(match.group('branch'))
57 def print_feature_diff(added_features, removed_features):
58 for feature in added_features:
59 print "+ %s" % feature
60 for feature in removed_features:
61 print "- %s" % feature
64 def historical_versions(os_string, channel):
65 url_pattern = "http://omahaproxy.appspot.com/history?os=%s&channel=%s"
66 url = url_pattern % (os_string, channel)
67 releases_csv = requests.get(url).text.strip("\n")
68 # Format: os,channel,version_string,date_string
69 lines = releases_csv.split('\n')
70 # As of June 2014, omahaproxy is now including headers:
71 assert(lines[0] == 'os,channel,version,timestamp')
72 # FIXME: We could replace this with more generic CSV parsing now that we have headers.
73 return [line.split(',')[2] for line in lines[1:]]
76 def feature_file_url_for_branch(branch):
77 path = features_path(branch)
78 if not path:
79 return None
80 return BRANCH_FORMAT % (branch, path)
83 def feature_file_for_branch(branch):
84 url = feature_file_url_for_branch(branch)
85 if not url:
86 return None
87 return parse_features_file(requests.get(url).text)
90 def historical_feature_tuples(os_string, channel):
91 feature_tuples = []
92 version_strings = reversed(historical_versions(os_string, channel))
93 seen_branches = set()
95 for version in version_strings:
96 branch = branch_from_version(version)
97 if branch in seen_branches:
98 continue
99 seen_branches.add(branch)
101 feature_file = feature_file_for_branch(branch)
102 if not feature_file:
103 continue
104 feature_tuple = (version, feature_file)
105 feature_tuples.append(feature_tuple)
106 return feature_tuples
109 class FeatureAuditor(object):
110 def __init__(self):
111 self.last_features = []
113 def add_version(self, version_name, feature_file):
114 features = stable_features(feature_file)
115 if self.last_features:
116 added_features = list(set(features) - set(self.last_features))
117 removed_features = list(set(self.last_features) - set(features))
119 print "\n%s:" % version_name
120 print_feature_diff(added_features, removed_features)
122 self.last_features = features
125 def active_feature_tuples(os_string):
126 feature_tuples = []
127 current_releases_url = "http://omahaproxy.appspot.com/all.json"
128 trains = requests.get(current_releases_url).json()
129 train = next(train for train in trains if train['os'] == os_string)
130 # FIXME: This is depending on the ordering of the json, we could
131 # use use sorted() with true_branch, but that would put None first.
132 for version in reversed(train['versions']):
133 # FIXME: This is lame to exclude stable, the caller should
134 # ignore it if it doesn't want it.
135 if version['channel'] == 'stable':
136 continue # handled by historical_feature_tuples
137 branch = version['true_branch']
138 if branch:
139 feature_file = feature_file_for_branch(branch)
140 else:
141 feature_file = parse_features_file(requests.get(TRUNK_URL).text)
143 name = "%(version)s %(channel)s" % version
144 feature_tuples.append((name, feature_file))
145 return feature_tuples
148 # FIXME: This only really needs feature_files.
149 def stale_features(tuples):
150 last_features = None
151 can_be_removed = set()
152 for _, feature_file in tuples:
153 features = stable_features(feature_file)
154 if last_features:
155 can_be_removed.update(set(features))
156 removed_features = list(set(last_features) - set(features))
157 can_be_removed.difference_update(set(removed_features))
158 last_features = features
159 return sorted(can_be_removed)
162 def main():
163 historical_tuples = historical_feature_tuples("win", "stable")
164 active_tuples = active_feature_tuples("win")
166 auditor = FeatureAuditor()
167 for version, feature_file in historical_tuples + active_tuples:
168 auditor.add_version(version, feature_file)
170 print "\nConsider for removal (have been stable for at least one release):"
171 for feature in stale_features(historical_tuples):
172 print feature
175 if __name__ == "__main__":
176 main()