blob: 9b7a1d32fe6ce6028154565a6f55c4061d5d8cbf [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''
Reads glic.mojom and outputs generated code to glic_api.ts.
Translates enums with a "// @generate glic_api" comment above them
and copies them into glic_api.ts. Comments from mojom files are
ignored if they have a '///' prefix, allowing for internal
documentation to be filtered out.
Ideally, we would output generated code to a new file, but this way is
less likely to break downstream users of glic_api.
'''
import argparse
import codecs
import io
import os
import re
import sys
GENERATE_GLIC_API_RE = re.compile(r'.*@generate glic_api')
IGNORE_LINE_RES = [
re.compile(r'.*([L]INT\.IfChange|[L]INT\.ThenChange)'),
re.compile(r'\s*// Next version:'),
re.compile(r'\s*///'),
GENERATE_GLIC_API_RE,
]
def _GetDirAbove(dirname: str):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
if not tail:
return None
if tail == dirname:
return path
SOURCE_DIR = _GetDirAbove('chrome')
# WARNING!
# Using mojo internal parser here, which is subject to change.
# mojo owners are NOT responsible for ensuring this script keeps working.
sys.path.append(
os.path.abspath(os.path.join(SOURCE_DIR, 'mojo/public/tools/mojom')), )
from mojom.parse import parser
from mojom.parse import ast
from mojom.generate import generator
def _ParseAst(mojom_abspath):
with codecs.open(mojom_abspath, encoding='utf-8') as f:
return parser.Parse(f.read(), mojom_abspath, with_comments=True)
class Converter:
def __init__(self):
self.out = io.StringIO()
mojom_files = [
'chrome/browser/glic/host/glic.mojom',
'chrome/common/actor_webui.mojom',
]
self.mojom_trees = [
_ParseAst(os.path.join(SOURCE_DIR, f)) for f in mojom_files
]
def PrintComments(self, node, indent=0):
if not node.comments_before:
return
for c in node.comments_before:
for line in c.value.splitlines():
line = line.strip()
if any([r.match(line) for r in IGNORE_LINE_RES]):
continue
self.Print(' ' * indent + line)
def LookupName(self, name, remap):
if name not in remap:
return generator.ToUpperSnakeCase(name)
new_name = remap[name]
del remap[name]
return new_name
def ConvertEnums(self, remappings):
for tree in self.mojom_trees:
for v in tree.definition_list:
if not isinstance(v, ast.Enum):
continue
if v.comments_before and any(
(GENERATE_GLIC_API_RE.match(comment.value)
for comment in v.comments_before)):
self.ConvertEnum(v, remappings.get(v.mojom_name.name, {}))
def ConvertEnum(self, enum, remap={}):
enum_name = enum.mojom_name.name
remap = dict(remap)
self.Print('///////////////////////////////////////////////')
self.Print('// WARNING - GENERATED FROM MOJOM, DO NOT EDIT.')
self.PrintComments(enum)
self.Print(f'export enum {enum_name} {{')
value = 0
for v in enum.enum_value_list:
value_name = self.LookupName(v.mojom_name.name, remap)
if value_name:
self.PrintComments(v, 2)
if v.value is not None:
value = int(v.value.value)
self.Print(f' {value_name} = {value},')
value += 1
self.Print(f'}}')
self.Print('')
if remap:
raise AssertionError('Unused remap for {enum_name}: {remap}')
def Print(self, *args, **kwargs):
print(*args, file=self.out, **kwargs)
def _ApplyChange(target_path, text, check_only):
with open(target_path, 'r') as f:
original_text = f.read()
START = '\n/// BEGIN_GENERATED - DO NOT MODIFY BELOW\n'
END = '\n/// END_GENERATED - DO NOT MODIFY ABOVE\n'
start_pos = original_text.find(START)
end_pos = original_text.find(END)
if start_pos < 0:
raise AssertionError(f'No BEGIN_GENERATED block in {target_path}')
if end_pos < 0:
raise AssertionError(f'No END_GENERATED block in {target_path}')
full_text = original_text[:start_pos] + START + text + original_text[
end_pos:]
if full_text == original_text:
return
if check_only:
print(f'{os.path.abspath(target_path)} is out of date,',
' run chrome/browser/resources/glic/glic_api_impl/generate.py',
file=sys.stderr)
sys.exit(1)
with open(target_path, 'w', newline='') as f:
f.write(full_text)
def _Main():
parser = argparse.ArgumentParser()
parser.add_argument('--check-only',
action='store_true',
help='Check if the output file is up to date.')
args = parser.parse_args()
c = Converter()
c.Print('''
// This block is generated by
// chrome/browser/resources/glic/glic_api_impl/generate.py
''')
c.ConvertEnums({
'WebClientMode': {
'kUnknown': None
},
'SettingsPageField': {
'kNone': None
},
'PerformActionsErrorReason': {
'kMissingTaskId': None,
'kInvalidProto': 'INVALID_ACTION_PROTO'
},
})
target_path = os.path.join(
SOURCE_DIR, 'chrome/browser/resources/glic/glic_api/glic_api.ts')
generated_text = c.out.getvalue()
_ApplyChange(target_path, generated_text, args.check_only)
if __name__ == '__main__':
_Main()