blob: fcd318f8e42a5b61842fc15d33e1f7f0494a43fe [file] [log] [blame]
#!/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', '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()