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 177 178 179 180 181 182 183 184 185 186 187 188
|
#!/usr/bin/env python3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper for quickly generating all known JS externs."""
import argparse
import os
import re
import sys
from compiler import GenerateSchema
# APIs with generated externs.
API_SOURCES = (
('chrome', 'common', 'apps', 'platform_apps', 'api'),
('chrome', 'common', 'extensions', 'api'),
('extensions', 'common', 'api'),
)
_EXTERNS_UPDATE_MESSAGE = """Please run one of:
src/ $ tools/json_schema_compiler/generate_all_externs.py
OR
src/ $ tools/json_schema_compiler/compiler.py\
%(source)s --root=. --generator=externs > %(externs)s"""
DIR = os.path.dirname(os.path.realpath(__file__))
REPO_ROOT = os.path.dirname(os.path.dirname(DIR))
# Import the helper module.
sys.path.insert(0, os.path.join(REPO_ROOT, 'extensions', 'common', 'api'))
from externs_checker import ExternsChecker
sys.path.pop(0)
class FakeChange:
"""Stand-in for PRESUBMIT input_api.change.
Enough to make ExternsChecker happy.
"""
@staticmethod
def RepositoryRoot():
return REPO_ROOT
class FakeInputApi:
"""Stand in for PRESUBMIT input_api.
Enough to make ExternsChecker happy.
"""
change = FakeChange()
os_path = os.path
re = re
@staticmethod
def PresubmitLocalPath():
return DIR
@staticmethod
def ReadFile(path):
with open(path) as fp:
return fp.read()
class FakeOutputApi:
"""Stand in for PRESUBMIT input_api.
Enough to make CheckExterns happy.
"""
class PresubmitResult:
def __init__(self, msg, long_text=None):
self.msg = msg
self.long_text = long_text
def Generate(input_api, output_api, force=False, dryrun=False):
"""(Re)generate all the externs."""
src_root = input_api.change.RepositoryRoot()
join = input_api.os_path.join
# Load the list of all generated externs.
api_pairs = {}
for api_source in API_SOURCES:
api_root = join(src_root, *api_source)
api_pairs.update(
ExternsChecker.ParseApiFileList(input_api, api_root=api_root))
# Unfortunately, our generator is still a bit buggy, so ignore externs that
# are known to be hand edited after the fact. We require people to add an
# explicit TODO marker bound to a known bug.
# TODO(vapier): Improve the toolchain enough to not require this.
re_disabled = input_api.re.compile(
r'^// TODO\(crbug\.com/[0-9]+\): '
r'Disable automatic extern generation until fixed\.$',
flags=input_api.re.M)
# Make sure each one is up-to-date with our toolchain.
ret = []
msg_len = 0
for source, externs in sorted(api_pairs.items()):
try:
old_data = input_api.ReadFile(externs)
except OSError:
old_data = ''
if not force and re_disabled.search(old_data):
continue
source_relpath = input_api.os_path.relpath(source, src_root)
externs_relpath = input_api.os_path.relpath(externs, src_root)
print('\r' + ' ' * msg_len, end='\r')
msg = 'Checking %s ...' % (source_relpath,)
msg_len = len(msg)
print(msg, end='')
sys.stdout.flush()
try:
new_data = GenerateSchema('externs', [source], src_root, None, '', '',
None, []) + '\n'
except Exception as e:
if not dryrun:
print('\n%s: %s' % (source_relpath, e))
ret.append(
output_api.PresubmitResult(
'%s: unable to generate' % (source_relpath,),
long_text=str(e)))
continue
# Ignore the first line (copyright) to avoid yearly thrashing.
if '\n' in old_data:
copyright, old_data = old_data.split('\n', 1)
assert 'Copyright' in copyright
copyright, new_data = new_data.split('\n', 1)
assert 'Copyright' in copyright
if old_data != new_data:
settings = {
'source': source_relpath,
'externs': externs_relpath,
}
ret.append(
output_api.PresubmitResult(
'%(source)s: file needs to be regenerated' % settings,
long_text=_EXTERNS_UPDATE_MESSAGE % settings))
if not dryrun:
print('\r' + ' ' * msg_len, end='\r')
msg_len = 0
print('Updating %s' % (externs_relpath,))
with open(externs, 'w', encoding='utf-8') as fp:
fp.write(copyright + '\n')
fp.write(new_data)
print('\r' + ' ' * msg_len, end='\r')
return ret
def get_parser():
"""Get CLI parser."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-n', '--dry-run', dest='dryrun', action='store_true',
help="Don't make changes; only show changed files")
parser.add_argument('-f', '--force', action='store_true',
help='Regenerate files even if they have a TODO '
'disabling generation')
return parser
def main(argv):
"""The main entry point for scripts."""
parser = get_parser()
opts = parser.parse_args(argv)
results = Generate(FakeInputApi(), FakeOutputApi(), force=opts.force,
dryrun=opts.dryrun)
if opts.dryrun and results:
for result in results:
print(result.msg + '\n' + result.long_text)
print()
else:
print('Done')
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
|