0
Files
src/tools/json_schema_compiler/generate_all_externs.py
Mike Frysinger 829eaa2da3 externs generator: require generated externs stay in sync
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}
2022-12-08 02:19:17 +00:00

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:]))