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