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:
16 # Source/core/page/RuntimeEnabledFeatures.in existed by 1547
17 # but was in an old format without status= arguments.
21 return "Source/core/page/RuntimeEnabledFeatures.in"
26 def parse_features_file(features_text
):
28 'status': ['stable', 'experimental', 'deprecated', 'test'],
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:
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
)
80 return BRANCH_FORMAT
% (branch
, path
)
83 def feature_file_for_branch(branch
):
84 url
= feature_file_url_for_branch(branch
)
87 return parse_features_file(requests
.get(url
).text
)
90 def historical_feature_tuples(os_string
, channel
):
92 version_strings
= reversed(historical_versions(os_string
, channel
))
95 for version
in version_strings
:
96 branch
= branch_from_version(version
)
97 if branch
in seen_branches
:
99 seen_branches
.add(branch
)
101 feature_file
= feature_file_for_branch(branch
)
104 feature_tuple
= (version
, feature_file
)
105 feature_tuples
.append(feature_tuple
)
106 return feature_tuples
109 class FeatureAuditor(object):
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
):
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']
139 feature_file
= feature_file_for_branch(branch
)
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
):
151 can_be_removed
= set()
152 for _
, feature_file
in tuples
:
153 features
= stable_features(feature_file
)
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
)
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
):
175 if __name__
== "__main__":