
Whenever we update this tool, make sure the changes are reflected in all the generated files. This makes it easier to review changes in here, and to make sure we don't force devs updating APIs to include a lot of unrelated/new changes. Then do a single refresh against all the externs so the tests pass. Bug: 469920 Change-Id: I88dcd46f187a2c71e47cbbc77012f96cb03b7c59 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2215902 Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org> Commit-Queue: Mike Frysinger <vapier@chromium.org> Cr-Commit-Position: refs/heads/main@{#1080726}
189 lines
5.2 KiB
Python
Executable File
189 lines
5.2 KiB
Python
Executable File
#!/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:]))
|