2021-08-09 15:42:31 +00:00
|
|
|
#!/usr/bin/env python3
|
2018-11-28 14:26:24 +00:00
|
|
|
# Copyright 2018 the V8 project authors. All rights reserved.
|
|
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
|
|
# found in the LICENSE file.
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
from datetime import datetime
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import sys
|
2021-08-09 15:42:31 +00:00
|
|
|
from pathlib import Path
|
2021-09-22 08:32:01 +00:00
|
|
|
import logging
|
|
|
|
from multiprocessing import Pool
|
2018-11-28 14:26:24 +00:00
|
|
|
|
|
|
|
RE_GITHASH = re.compile(r"^[0-9a-f]{40}")
|
|
|
|
RE_AUTHOR_TIME = re.compile(r"^author-time (\d+)$")
|
|
|
|
RE_FILENAME = re.compile(r"^filename (.+)$")
|
|
|
|
|
2021-08-09 15:42:31 +00:00
|
|
|
VERSION_CACHE = dict()
|
|
|
|
RE_VERSION_MAJOR = re.compile(r".*V8_MAJOR_VERSION ([0-9]+)")
|
|
|
|
RE_VERSION_MINOR = re.compile(r".*V8_MINOR_VERSION ([0-9]+)")
|
|
|
|
|
2020-12-30 23:51:01 +00:00
|
|
|
RE_MACRO_END = re.compile(r"\);")
|
2018-11-28 14:26:24 +00:00
|
|
|
RE_DEPRECATE_MACRO = re.compile(r"\(.*?,(.*)\);", re.MULTILINE)
|
|
|
|
|
2020-12-30 23:51:01 +00:00
|
|
|
|
2021-09-22 08:32:01 +00:00
|
|
|
class HeaderFile(object):
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
self.blame_list = self.get_blame_list()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_api_header_files(clazz, options):
|
|
|
|
files = subprocess.check_output(
|
|
|
|
['git', 'ls-tree', '--name-only', '-r', 'HEAD', options.include_dir],
|
|
|
|
encoding='UTF-8')
|
|
|
|
files = filter(lambda l: l.endswith('.h'), files.splitlines())
|
|
|
|
with Pool(processes=24) as pool:
|
|
|
|
return pool.map(HeaderFile, files)
|
|
|
|
|
|
|
|
def extract_version(self, hash):
|
|
|
|
if hash in VERSION_CACHE:
|
|
|
|
return VERSION_CACHE[hash]
|
|
|
|
if hash == '0000000000000000000000000000000000000000':
|
|
|
|
return 'HEAD'
|
|
|
|
result = subprocess.check_output(
|
|
|
|
['git', 'show', f"{hash}:include/v8-version.h"], encoding='UTF-8')
|
|
|
|
major = RE_VERSION_MAJOR.search(result).group(1)
|
|
|
|
minor = RE_VERSION_MINOR.search(result).group(1)
|
|
|
|
version = f"{major}.{minor}"
|
|
|
|
VERSION_CACHE[hash] = version
|
|
|
|
return version
|
|
|
|
|
|
|
|
def get_blame_list(self):
|
|
|
|
logging.info(f"blame list for {self.path}")
|
|
|
|
result = subprocess.check_output(
|
|
|
|
['git', 'blame', '-t', '--line-porcelain', self.path],
|
|
|
|
encoding='UTF-8')
|
|
|
|
line_iter = iter(result.splitlines())
|
|
|
|
blame_list = list()
|
|
|
|
current_blame = None
|
|
|
|
while True:
|
|
|
|
line = next(line_iter, None)
|
|
|
|
if line is None:
|
|
|
|
break
|
|
|
|
if RE_GITHASH.match(line):
|
|
|
|
if current_blame is not None:
|
|
|
|
blame_list.append(current_blame)
|
|
|
|
hash = line.split(" ")[0]
|
|
|
|
current_blame = {
|
|
|
|
'datetime': 0,
|
|
|
|
'filename': None,
|
|
|
|
'content': None,
|
|
|
|
'hash': hash
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
match = RE_AUTHOR_TIME.match(line)
|
|
|
|
if match:
|
|
|
|
current_blame['datetime'] = datetime.fromtimestamp(
|
|
|
|
int(match.groups()[0]))
|
|
|
|
continue
|
|
|
|
match = RE_FILENAME.match(line)
|
|
|
|
if match:
|
|
|
|
current_blame['filename'] = match.groups()[0]
|
|
|
|
current_blame['content'] = next(line_iter).strip()
|
|
|
|
continue
|
|
|
|
blame_list.append(current_blame)
|
|
|
|
return blame_list
|
|
|
|
|
|
|
|
def filter_and_print(self, macro, options):
|
|
|
|
before = options.before
|
|
|
|
index = 0
|
|
|
|
re_macro = re.compile(macro)
|
|
|
|
deprecated = list()
|
|
|
|
while index < len(self.blame_list):
|
|
|
|
blame = self.blame_list[index]
|
|
|
|
line = blame['content']
|
|
|
|
if line.startswith("#") or line.startswith("//"):
|
|
|
|
index += 1
|
|
|
|
continue
|
|
|
|
commit_datetime = blame['datetime']
|
|
|
|
if commit_datetime >= before:
|
|
|
|
index += 1
|
|
|
|
continue
|
|
|
|
commit_hash = blame['hash']
|
|
|
|
match = re_macro.search(line)
|
|
|
|
if match:
|
|
|
|
pos = match.end()
|
|
|
|
start = -1
|
|
|
|
parens = 0
|
|
|
|
while True:
|
|
|
|
if pos >= len(line):
|
|
|
|
# Extend to next line
|
|
|
|
index = index + 1
|
|
|
|
blame = self.blame_list[index]
|
|
|
|
line = line + blame['content']
|
|
|
|
if line[pos] == '(':
|
|
|
|
parens = parens + 1
|
|
|
|
elif line[pos] == ')':
|
|
|
|
parens = parens - 1
|
|
|
|
if parens == 0:
|
|
|
|
# Exclude closing ")
|
|
|
|
pos = pos - 2
|
|
|
|
break
|
|
|
|
elif line[pos] == '"' and start == -1:
|
|
|
|
start = pos + 1
|
|
|
|
pos = pos + 1
|
|
|
|
# Extract content and replace double quotes from merged lines
|
|
|
|
content = line[start:pos].strip().replace('""', '')
|
|
|
|
deprecated.append((index + 1, commit_datetime, commit_hash, content))
|
|
|
|
index = index + 1
|
|
|
|
if len(deprecated) == 0: return
|
|
|
|
for linenumber, commit_datetime, commit_hash, content in deprecated:
|
|
|
|
commit_date = commit_datetime.date()
|
|
|
|
file_position = (f"{self.path}:{linenumber}").rjust(40)
|
|
|
|
print(f" {file_position}\t{commit_date}\t{commit_hash[:8]}"
|
|
|
|
f"\t{self.extract_version(commit_hash)}\t{content}")
|
|
|
|
return len(deprecated)
|
2018-11-28 14:26:24 +00:00
|
|
|
|
2020-12-30 23:51:01 +00:00
|
|
|
|
2021-08-09 15:42:31 +00:00
|
|
|
def parse_options(args):
|
2020-12-30 23:51:01 +00:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description="Collect deprecation statistics")
|
2021-09-22 08:32:01 +00:00
|
|
|
parser.add_argument("include_dir", nargs='?', help="Path to includes dir")
|
2018-11-28 14:26:24 +00:00
|
|
|
parser.add_argument("--before", help="Filter by date")
|
2021-09-22 08:32:01 +00:00
|
|
|
parser.add_argument("--verbose",
|
|
|
|
"-v",
|
|
|
|
help="Verbose logging",
|
|
|
|
action="store_true")
|
2018-11-28 14:26:24 +00:00
|
|
|
options = parser.parse_args(args)
|
2021-09-22 08:32:01 +00:00
|
|
|
if options.verbose:
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
2018-11-28 14:26:24 +00:00
|
|
|
if options.before:
|
|
|
|
options.before = datetime.strptime(options.before, '%Y-%m-%d')
|
|
|
|
else:
|
|
|
|
options.before = datetime.now()
|
2021-09-22 08:32:01 +00:00
|
|
|
if options.include_dir is None:
|
2021-08-09 15:42:31 +00:00
|
|
|
base_path = Path(__file__).parent.parent
|
2021-09-22 08:32:01 +00:00
|
|
|
options.include_dir = str((base_path / 'include').relative_to(base_path))
|
2018-11-28 14:26:24 +00:00
|
|
|
return options
|
|
|
|
|
2020-12-30 23:51:01 +00:00
|
|
|
|
2021-08-09 15:42:31 +00:00
|
|
|
def main(args):
|
|
|
|
options = parse_options(args)
|
2021-09-22 08:32:01 +00:00
|
|
|
header_files = HeaderFile.get_api_header_files(options)
|
|
|
|
print("V8_DEPRECATE_SOON:")
|
|
|
|
for header in header_files:
|
|
|
|
header.filter_and_print("V8_DEPRECATE_SOON", options)
|
2021-08-09 15:42:31 +00:00
|
|
|
print("\n")
|
2021-09-22 08:32:01 +00:00
|
|
|
print("V8_DEPRECATED:")
|
|
|
|
for header in header_files:
|
|
|
|
header.filter_and_print("V8_DEPRECATED", options)
|
2020-12-30 23:51:01 +00:00
|
|
|
|
2018-11-28 14:26:24 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2021-08-09 15:42:31 +00:00
|
|
|
main(sys.argv[1:])
|