File: generate_all_externs.py

package info (click to toggle)
chromium 120.0.6099.224-1~deb11u1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,112,112 kB
  • sloc: cpp: 32,907,025; ansic: 8,148,123; javascript: 3,679,536; python: 2,031,248; asm: 959,718; java: 804,675; xml: 617,256; sh: 111,417; objc: 100,835; perl: 88,443; cs: 53,032; makefile: 29,579; fortran: 24,137; php: 21,162; tcl: 21,147; sql: 20,809; ruby: 17,735; pascal: 12,864; yacc: 8,045; lisp: 3,388; lex: 1,323; ada: 727; awk: 329; jsp: 267; csh: 117; exp: 43; sed: 37
file content (188 lines) | stat: -rwxr-xr-x 5,321 bytes parent folder | download | duplicates (2)
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:]))