xref: /aosp_15_r20/external/cronet/build/gn_helpers.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker# Copyright 2014 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker"""Helper functions useful when writing scripts that integrate with GN.
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard WorkerThe main functions are ToGNString() and FromGNString(), to convert between
8*6777b538SAndroid Build Coastguard Workerserialized GN veriables and Python variables.
9*6777b538SAndroid Build Coastguard Worker
10*6777b538SAndroid Build Coastguard WorkerTo use in an arbitrary Python file in the build:
11*6777b538SAndroid Build Coastguard Worker
12*6777b538SAndroid Build Coastguard Worker  import os
13*6777b538SAndroid Build Coastguard Worker  import sys
14*6777b538SAndroid Build Coastguard Worker
15*6777b538SAndroid Build Coastguard Worker  sys.path.append(os.path.join(os.path.dirname(__file__),
16*6777b538SAndroid Build Coastguard Worker                               os.pardir, os.pardir, 'build'))
17*6777b538SAndroid Build Coastguard Worker  import gn_helpers
18*6777b538SAndroid Build Coastguard Worker
19*6777b538SAndroid Build Coastguard WorkerWhere the sequence of parameters to join is the relative path from your source
20*6777b538SAndroid Build Coastguard Workerfile to the build directory.
21*6777b538SAndroid Build Coastguard Worker"""
22*6777b538SAndroid Build Coastguard Worker
23*6777b538SAndroid Build Coastguard Workerimport json
24*6777b538SAndroid Build Coastguard Workerimport os
25*6777b538SAndroid Build Coastguard Workerimport re
26*6777b538SAndroid Build Coastguard Workerimport shlex
27*6777b538SAndroid Build Coastguard Workerimport shutil
28*6777b538SAndroid Build Coastguard Workerimport sys
29*6777b538SAndroid Build Coastguard Worker
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker_CHROMIUM_ROOT = os.path.abspath(
32*6777b538SAndroid Build Coastguard Worker    os.path.join(os.path.dirname(__file__), os.pardir))
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard WorkerBUILD_VARS_FILENAME = 'build_vars.json'
35*6777b538SAndroid Build Coastguard WorkerIMPORT_RE = re.compile(r'^import\("//(\S+)"\)')
36*6777b538SAndroid Build Coastguard Worker
37*6777b538SAndroid Build Coastguard Worker
38*6777b538SAndroid Build Coastguard Workerclass GNError(Exception):
39*6777b538SAndroid Build Coastguard Worker  pass
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker
42*6777b538SAndroid Build Coastguard Worker# Computes ASCII code of an element of encoded Python 2 str / Python 3 bytes.
43*6777b538SAndroid Build Coastguard Worker_Ord = ord if sys.version_info.major < 3 else lambda c: c
44*6777b538SAndroid Build Coastguard Worker
45*6777b538SAndroid Build Coastguard Worker
46*6777b538SAndroid Build Coastguard Workerdef _TranslateToGnChars(s):
47*6777b538SAndroid Build Coastguard Worker  for decoded_ch in s.encode('utf-8'):  # str in Python 2, bytes in Python 3.
48*6777b538SAndroid Build Coastguard Worker    code = _Ord(decoded_ch)  # int
49*6777b538SAndroid Build Coastguard Worker    if code in (34, 36, 92):  # For '"', '$', or '\\'.
50*6777b538SAndroid Build Coastguard Worker      yield '\\' + chr(code)
51*6777b538SAndroid Build Coastguard Worker    elif 32 <= code < 127:
52*6777b538SAndroid Build Coastguard Worker      yield chr(code)
53*6777b538SAndroid Build Coastguard Worker    else:
54*6777b538SAndroid Build Coastguard Worker      yield '$0x%02X' % code
55*6777b538SAndroid Build Coastguard Worker
56*6777b538SAndroid Build Coastguard Worker
57*6777b538SAndroid Build Coastguard Workerdef ToGNString(value, pretty=False):
58*6777b538SAndroid Build Coastguard Worker  """Returns a stringified GN equivalent of a Python value.
59*6777b538SAndroid Build Coastguard Worker
60*6777b538SAndroid Build Coastguard Worker  Args:
61*6777b538SAndroid Build Coastguard Worker    value: The Python value to convert.
62*6777b538SAndroid Build Coastguard Worker    pretty: Whether to pretty print. If true, then non-empty lists are rendered
63*6777b538SAndroid Build Coastguard Worker        recursively with one item per line, with indents. Otherwise lists are
64*6777b538SAndroid Build Coastguard Worker        rendered without new line.
65*6777b538SAndroid Build Coastguard Worker  Returns:
66*6777b538SAndroid Build Coastguard Worker    The stringified GN equivalent to |value|.
67*6777b538SAndroid Build Coastguard Worker
68*6777b538SAndroid Build Coastguard Worker  Raises:
69*6777b538SAndroid Build Coastguard Worker    GNError: |value| cannot be printed to GN.
70*6777b538SAndroid Build Coastguard Worker  """
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Worker  if sys.version_info.major < 3:
73*6777b538SAndroid Build Coastguard Worker    basestring_compat = basestring
74*6777b538SAndroid Build Coastguard Worker  else:
75*6777b538SAndroid Build Coastguard Worker    basestring_compat = str
76*6777b538SAndroid Build Coastguard Worker
77*6777b538SAndroid Build Coastguard Worker  # Emits all output tokens without intervening whitespaces.
78*6777b538SAndroid Build Coastguard Worker  def GenerateTokens(v, level):
79*6777b538SAndroid Build Coastguard Worker    if isinstance(v, basestring_compat):
80*6777b538SAndroid Build Coastguard Worker      yield '"' + ''.join(_TranslateToGnChars(v)) + '"'
81*6777b538SAndroid Build Coastguard Worker
82*6777b538SAndroid Build Coastguard Worker    elif isinstance(v, bool):
83*6777b538SAndroid Build Coastguard Worker      yield 'true' if v else 'false'
84*6777b538SAndroid Build Coastguard Worker
85*6777b538SAndroid Build Coastguard Worker    elif isinstance(v, int):
86*6777b538SAndroid Build Coastguard Worker      yield str(v)
87*6777b538SAndroid Build Coastguard Worker
88*6777b538SAndroid Build Coastguard Worker    elif isinstance(v, list):
89*6777b538SAndroid Build Coastguard Worker      yield '['
90*6777b538SAndroid Build Coastguard Worker      for i, item in enumerate(v):
91*6777b538SAndroid Build Coastguard Worker        if i > 0:
92*6777b538SAndroid Build Coastguard Worker          yield ','
93*6777b538SAndroid Build Coastguard Worker        for tok in GenerateTokens(item, level + 1):
94*6777b538SAndroid Build Coastguard Worker          yield tok
95*6777b538SAndroid Build Coastguard Worker      yield ']'
96*6777b538SAndroid Build Coastguard Worker
97*6777b538SAndroid Build Coastguard Worker    elif isinstance(v, dict):
98*6777b538SAndroid Build Coastguard Worker      if level > 0:
99*6777b538SAndroid Build Coastguard Worker        yield '{'
100*6777b538SAndroid Build Coastguard Worker      for key in sorted(v):
101*6777b538SAndroid Build Coastguard Worker        if not isinstance(key, basestring_compat):
102*6777b538SAndroid Build Coastguard Worker          raise GNError('Dictionary key is not a string.')
103*6777b538SAndroid Build Coastguard Worker        if not key or key[0].isdigit() or not key.replace('_', '').isalnum():
104*6777b538SAndroid Build Coastguard Worker          raise GNError('Dictionary key is not a valid GN identifier.')
105*6777b538SAndroid Build Coastguard Worker        yield key  # No quotations.
106*6777b538SAndroid Build Coastguard Worker        yield '='
107*6777b538SAndroid Build Coastguard Worker        for tok in GenerateTokens(v[key], level + 1):
108*6777b538SAndroid Build Coastguard Worker          yield tok
109*6777b538SAndroid Build Coastguard Worker      if level > 0:
110*6777b538SAndroid Build Coastguard Worker        yield '}'
111*6777b538SAndroid Build Coastguard Worker
112*6777b538SAndroid Build Coastguard Worker    else:  # Not supporting float: Add only when needed.
113*6777b538SAndroid Build Coastguard Worker      raise GNError('Unsupported type when printing to GN.')
114*6777b538SAndroid Build Coastguard Worker
115*6777b538SAndroid Build Coastguard Worker  can_start = lambda tok: tok and tok not in ',}]='
116*6777b538SAndroid Build Coastguard Worker  can_end = lambda tok: tok and tok not in ',{[='
117*6777b538SAndroid Build Coastguard Worker
118*6777b538SAndroid Build Coastguard Worker  # Adds whitespaces, trying to keep everything (except dicts) in 1 line.
119*6777b538SAndroid Build Coastguard Worker  def PlainGlue(gen):
120*6777b538SAndroid Build Coastguard Worker    prev_tok = None
121*6777b538SAndroid Build Coastguard Worker    for i, tok in enumerate(gen):
122*6777b538SAndroid Build Coastguard Worker      if i > 0:
123*6777b538SAndroid Build Coastguard Worker        if can_end(prev_tok) and can_start(tok):
124*6777b538SAndroid Build Coastguard Worker          yield '\n'  # New dict item.
125*6777b538SAndroid Build Coastguard Worker        elif prev_tok == '[' and tok == ']':
126*6777b538SAndroid Build Coastguard Worker          yield '  '  # Special case for [].
127*6777b538SAndroid Build Coastguard Worker        elif tok != ',':
128*6777b538SAndroid Build Coastguard Worker          yield ' '
129*6777b538SAndroid Build Coastguard Worker      yield tok
130*6777b538SAndroid Build Coastguard Worker      prev_tok = tok
131*6777b538SAndroid Build Coastguard Worker
132*6777b538SAndroid Build Coastguard Worker  # Adds whitespaces so non-empty lists can span multiple lines, with indent.
133*6777b538SAndroid Build Coastguard Worker  def PrettyGlue(gen):
134*6777b538SAndroid Build Coastguard Worker    prev_tok = None
135*6777b538SAndroid Build Coastguard Worker    level = 0
136*6777b538SAndroid Build Coastguard Worker    for i, tok in enumerate(gen):
137*6777b538SAndroid Build Coastguard Worker      if i > 0:
138*6777b538SAndroid Build Coastguard Worker        if can_end(prev_tok) and can_start(tok):
139*6777b538SAndroid Build Coastguard Worker          yield '\n' + '  ' * level  # New dict item.
140*6777b538SAndroid Build Coastguard Worker        elif tok == '=' or prev_tok in '=':
141*6777b538SAndroid Build Coastguard Worker          yield ' '  # Separator before and after '=', on same line.
142*6777b538SAndroid Build Coastguard Worker      if tok in ']}':
143*6777b538SAndroid Build Coastguard Worker        level -= 1
144*6777b538SAndroid Build Coastguard Worker      # Exclude '[]' and '{}' cases.
145*6777b538SAndroid Build Coastguard Worker      if int(prev_tok == '[') + int(tok == ']') == 1 or \
146*6777b538SAndroid Build Coastguard Worker         int(prev_tok == '{') + int(tok == '}') == 1:
147*6777b538SAndroid Build Coastguard Worker        yield '\n' + '  ' * level
148*6777b538SAndroid Build Coastguard Worker      yield tok
149*6777b538SAndroid Build Coastguard Worker      if tok in '[{':
150*6777b538SAndroid Build Coastguard Worker        level += 1
151*6777b538SAndroid Build Coastguard Worker      if tok == ',':
152*6777b538SAndroid Build Coastguard Worker        yield '\n' + '  ' * level
153*6777b538SAndroid Build Coastguard Worker      prev_tok = tok
154*6777b538SAndroid Build Coastguard Worker
155*6777b538SAndroid Build Coastguard Worker  token_gen = GenerateTokens(value, 0)
156*6777b538SAndroid Build Coastguard Worker  ret = ''.join((PrettyGlue if pretty else PlainGlue)(token_gen))
157*6777b538SAndroid Build Coastguard Worker  # Add terminating '\n' for dict |value| or multi-line output.
158*6777b538SAndroid Build Coastguard Worker  if isinstance(value, dict) or '\n' in ret:
159*6777b538SAndroid Build Coastguard Worker    return ret + '\n'
160*6777b538SAndroid Build Coastguard Worker  return ret
161*6777b538SAndroid Build Coastguard Worker
162*6777b538SAndroid Build Coastguard Worker
163*6777b538SAndroid Build Coastguard Workerdef FromGNString(input_string):
164*6777b538SAndroid Build Coastguard Worker  """Converts the input string from a GN serialized value to Python values.
165*6777b538SAndroid Build Coastguard Worker
166*6777b538SAndroid Build Coastguard Worker  For details on supported types see GNValueParser.Parse() below.
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Worker  If your GN script did:
169*6777b538SAndroid Build Coastguard Worker    something = [ "file1", "file2" ]
170*6777b538SAndroid Build Coastguard Worker    args = [ "--values=$something" ]
171*6777b538SAndroid Build Coastguard Worker  The command line would look something like:
172*6777b538SAndroid Build Coastguard Worker    --values="[ \"file1\", \"file2\" ]"
173*6777b538SAndroid Build Coastguard Worker  Which when interpreted as a command line gives the value:
174*6777b538SAndroid Build Coastguard Worker    [ "file1", "file2" ]
175*6777b538SAndroid Build Coastguard Worker
176*6777b538SAndroid Build Coastguard Worker  You can parse this into a Python list using GN rules with:
177*6777b538SAndroid Build Coastguard Worker    input_values = FromGNValues(options.values)
178*6777b538SAndroid Build Coastguard Worker  Although the Python 'ast' module will parse many forms of such input, it
179*6777b538SAndroid Build Coastguard Worker  will not handle GN escaping properly, nor GN booleans. You should use this
180*6777b538SAndroid Build Coastguard Worker  function instead.
181*6777b538SAndroid Build Coastguard Worker
182*6777b538SAndroid Build Coastguard Worker
183*6777b538SAndroid Build Coastguard Worker  A NOTE ON STRING HANDLING:
184*6777b538SAndroid Build Coastguard Worker
185*6777b538SAndroid Build Coastguard Worker  If you just pass a string on the command line to your Python script, or use
186*6777b538SAndroid Build Coastguard Worker  string interpolation on a string variable, the strings will not be quoted:
187*6777b538SAndroid Build Coastguard Worker    str = "asdf"
188*6777b538SAndroid Build Coastguard Worker    args = [ str, "--value=$str" ]
189*6777b538SAndroid Build Coastguard Worker  Will yield the command line:
190*6777b538SAndroid Build Coastguard Worker    asdf --value=asdf
191*6777b538SAndroid Build Coastguard Worker  The unquoted asdf string will not be valid input to this function, which
192*6777b538SAndroid Build Coastguard Worker  accepts only quoted strings like GN scripts. In such cases, you can just use
193*6777b538SAndroid Build Coastguard Worker  the Python string literal directly.
194*6777b538SAndroid Build Coastguard Worker
195*6777b538SAndroid Build Coastguard Worker  The main use cases for this is for other types, in particular lists. When
196*6777b538SAndroid Build Coastguard Worker  using string interpolation on a list (as in the top example) the embedded
197*6777b538SAndroid Build Coastguard Worker  strings will be quoted and escaped according to GN rules so the list can be
198*6777b538SAndroid Build Coastguard Worker  re-parsed to get the same result.
199*6777b538SAndroid Build Coastguard Worker  """
200*6777b538SAndroid Build Coastguard Worker  parser = GNValueParser(input_string)
201*6777b538SAndroid Build Coastguard Worker  return parser.Parse()
202*6777b538SAndroid Build Coastguard Worker
203*6777b538SAndroid Build Coastguard Worker
204*6777b538SAndroid Build Coastguard Workerdef FromGNArgs(input_string):
205*6777b538SAndroid Build Coastguard Worker  """Converts a string with a bunch of gn arg assignments into a Python dict.
206*6777b538SAndroid Build Coastguard Worker
207*6777b538SAndroid Build Coastguard Worker  Given a whitespace-separated list of
208*6777b538SAndroid Build Coastguard Worker
209*6777b538SAndroid Build Coastguard Worker    <ident> = (integer | string | boolean | <list of the former>)
210*6777b538SAndroid Build Coastguard Worker
211*6777b538SAndroid Build Coastguard Worker  gn assignments, this returns a Python dict, i.e.:
212*6777b538SAndroid Build Coastguard Worker
213*6777b538SAndroid Build Coastguard Worker    FromGNArgs('foo=true\nbar=1\n') -> { 'foo': True, 'bar': 1 }.
214*6777b538SAndroid Build Coastguard Worker
215*6777b538SAndroid Build Coastguard Worker  Only simple types and lists supported; variables, structs, calls
216*6777b538SAndroid Build Coastguard Worker  and other, more complicated things are not.
217*6777b538SAndroid Build Coastguard Worker
218*6777b538SAndroid Build Coastguard Worker  This routine is meant to handle only the simple sorts of values that
219*6777b538SAndroid Build Coastguard Worker  arise in parsing --args.
220*6777b538SAndroid Build Coastguard Worker  """
221*6777b538SAndroid Build Coastguard Worker  parser = GNValueParser(input_string)
222*6777b538SAndroid Build Coastguard Worker  return parser.ParseArgs()
223*6777b538SAndroid Build Coastguard Worker
224*6777b538SAndroid Build Coastguard Worker
225*6777b538SAndroid Build Coastguard Workerdef UnescapeGNString(value):
226*6777b538SAndroid Build Coastguard Worker  """Given a string with GN escaping, returns the unescaped string.
227*6777b538SAndroid Build Coastguard Worker
228*6777b538SAndroid Build Coastguard Worker  Be careful not to feed with input from a Python parsing function like
229*6777b538SAndroid Build Coastguard Worker  'ast' because it will do Python unescaping, which will be incorrect when
230*6777b538SAndroid Build Coastguard Worker  fed into the GN unescaper.
231*6777b538SAndroid Build Coastguard Worker
232*6777b538SAndroid Build Coastguard Worker  Args:
233*6777b538SAndroid Build Coastguard Worker    value: Input string to unescape.
234*6777b538SAndroid Build Coastguard Worker  """
235*6777b538SAndroid Build Coastguard Worker  result = ''
236*6777b538SAndroid Build Coastguard Worker  i = 0
237*6777b538SAndroid Build Coastguard Worker  while i < len(value):
238*6777b538SAndroid Build Coastguard Worker    if value[i] == '\\':
239*6777b538SAndroid Build Coastguard Worker      if i < len(value) - 1:
240*6777b538SAndroid Build Coastguard Worker        next_char = value[i + 1]
241*6777b538SAndroid Build Coastguard Worker        if next_char in ('$', '"', '\\'):
242*6777b538SAndroid Build Coastguard Worker          # These are the escaped characters GN supports.
243*6777b538SAndroid Build Coastguard Worker          result += next_char
244*6777b538SAndroid Build Coastguard Worker          i += 1
245*6777b538SAndroid Build Coastguard Worker        else:
246*6777b538SAndroid Build Coastguard Worker          # Any other backslash is a literal.
247*6777b538SAndroid Build Coastguard Worker          result += '\\'
248*6777b538SAndroid Build Coastguard Worker    else:
249*6777b538SAndroid Build Coastguard Worker      result += value[i]
250*6777b538SAndroid Build Coastguard Worker    i += 1
251*6777b538SAndroid Build Coastguard Worker  return result
252*6777b538SAndroid Build Coastguard Worker
253*6777b538SAndroid Build Coastguard Worker
254*6777b538SAndroid Build Coastguard Workerdef _IsDigitOrMinus(char):
255*6777b538SAndroid Build Coastguard Worker  return char in '-0123456789'
256*6777b538SAndroid Build Coastguard Worker
257*6777b538SAndroid Build Coastguard Worker
258*6777b538SAndroid Build Coastguard Workerclass GNValueParser(object):
259*6777b538SAndroid Build Coastguard Worker  """Duplicates GN parsing of values and converts to Python types.
260*6777b538SAndroid Build Coastguard Worker
261*6777b538SAndroid Build Coastguard Worker  Normally you would use the wrapper function FromGNValue() below.
262*6777b538SAndroid Build Coastguard Worker
263*6777b538SAndroid Build Coastguard Worker  If you expect input as a specific type, you can also call one of the Parse*
264*6777b538SAndroid Build Coastguard Worker  functions directly. All functions throw GNError on invalid input.
265*6777b538SAndroid Build Coastguard Worker  """
266*6777b538SAndroid Build Coastguard Worker
267*6777b538SAndroid Build Coastguard Worker  def __init__(self, string, checkout_root=_CHROMIUM_ROOT):
268*6777b538SAndroid Build Coastguard Worker    self.input = string
269*6777b538SAndroid Build Coastguard Worker    self.cur = 0
270*6777b538SAndroid Build Coastguard Worker    self.checkout_root = checkout_root
271*6777b538SAndroid Build Coastguard Worker
272*6777b538SAndroid Build Coastguard Worker  def IsDone(self):
273*6777b538SAndroid Build Coastguard Worker    return self.cur == len(self.input)
274*6777b538SAndroid Build Coastguard Worker
275*6777b538SAndroid Build Coastguard Worker  def ReplaceImports(self):
276*6777b538SAndroid Build Coastguard Worker    """Replaces import(...) lines with the contents of the imports.
277*6777b538SAndroid Build Coastguard Worker
278*6777b538SAndroid Build Coastguard Worker    Recurses on itself until there are no imports remaining, in the case of
279*6777b538SAndroid Build Coastguard Worker    nested imports.
280*6777b538SAndroid Build Coastguard Worker    """
281*6777b538SAndroid Build Coastguard Worker    lines = self.input.splitlines()
282*6777b538SAndroid Build Coastguard Worker    if not any(line.startswith('import(') for line in lines):
283*6777b538SAndroid Build Coastguard Worker      return
284*6777b538SAndroid Build Coastguard Worker    for line in lines:
285*6777b538SAndroid Build Coastguard Worker      if not line.startswith('import('):
286*6777b538SAndroid Build Coastguard Worker        continue
287*6777b538SAndroid Build Coastguard Worker      regex_match = IMPORT_RE.match(line)
288*6777b538SAndroid Build Coastguard Worker      if not regex_match:
289*6777b538SAndroid Build Coastguard Worker        raise GNError('Not a valid import string: %s' % line)
290*6777b538SAndroid Build Coastguard Worker      import_path = os.path.join(self.checkout_root, regex_match.group(1))
291*6777b538SAndroid Build Coastguard Worker      with open(import_path) as f:
292*6777b538SAndroid Build Coastguard Worker        imported_args = f.read()
293*6777b538SAndroid Build Coastguard Worker      self.input = self.input.replace(line, imported_args)
294*6777b538SAndroid Build Coastguard Worker    # Call ourselves again if we've just replaced an import() with additional
295*6777b538SAndroid Build Coastguard Worker    # imports.
296*6777b538SAndroid Build Coastguard Worker    self.ReplaceImports()
297*6777b538SAndroid Build Coastguard Worker
298*6777b538SAndroid Build Coastguard Worker
299*6777b538SAndroid Build Coastguard Worker  def _ConsumeWhitespace(self):
300*6777b538SAndroid Build Coastguard Worker    while not self.IsDone() and self.input[self.cur] in ' \t\n':
301*6777b538SAndroid Build Coastguard Worker      self.cur += 1
302*6777b538SAndroid Build Coastguard Worker
303*6777b538SAndroid Build Coastguard Worker  def ConsumeCommentAndWhitespace(self):
304*6777b538SAndroid Build Coastguard Worker    self._ConsumeWhitespace()
305*6777b538SAndroid Build Coastguard Worker
306*6777b538SAndroid Build Coastguard Worker    # Consume each comment, line by line.
307*6777b538SAndroid Build Coastguard Worker    while not self.IsDone() and self.input[self.cur] == '#':
308*6777b538SAndroid Build Coastguard Worker      # Consume the rest of the comment, up until the end of the line.
309*6777b538SAndroid Build Coastguard Worker      while not self.IsDone() and self.input[self.cur] != '\n':
310*6777b538SAndroid Build Coastguard Worker        self.cur += 1
311*6777b538SAndroid Build Coastguard Worker      # Move the cursor to the next line (if there is one).
312*6777b538SAndroid Build Coastguard Worker      if not self.IsDone():
313*6777b538SAndroid Build Coastguard Worker        self.cur += 1
314*6777b538SAndroid Build Coastguard Worker
315*6777b538SAndroid Build Coastguard Worker      self._ConsumeWhitespace()
316*6777b538SAndroid Build Coastguard Worker
317*6777b538SAndroid Build Coastguard Worker  def Parse(self):
318*6777b538SAndroid Build Coastguard Worker    """Converts a string representing a printed GN value to the Python type.
319*6777b538SAndroid Build Coastguard Worker
320*6777b538SAndroid Build Coastguard Worker    See additional usage notes on FromGNString() above.
321*6777b538SAndroid Build Coastguard Worker
322*6777b538SAndroid Build Coastguard Worker    * GN booleans ('true', 'false') will be converted to Python booleans.
323*6777b538SAndroid Build Coastguard Worker
324*6777b538SAndroid Build Coastguard Worker    * GN numbers ('123') will be converted to Python numbers.
325*6777b538SAndroid Build Coastguard Worker
326*6777b538SAndroid Build Coastguard Worker    * GN strings (double-quoted as in '"asdf"') will be converted to Python
327*6777b538SAndroid Build Coastguard Worker      strings with GN escaping rules. GN string interpolation (embedded
328*6777b538SAndroid Build Coastguard Worker      variables preceded by $) are not supported and will be returned as
329*6777b538SAndroid Build Coastguard Worker      literals.
330*6777b538SAndroid Build Coastguard Worker
331*6777b538SAndroid Build Coastguard Worker    * GN lists ('[1, "asdf", 3]') will be converted to Python lists.
332*6777b538SAndroid Build Coastguard Worker
333*6777b538SAndroid Build Coastguard Worker    * GN scopes ('{ ... }') are not supported.
334*6777b538SAndroid Build Coastguard Worker
335*6777b538SAndroid Build Coastguard Worker    Raises:
336*6777b538SAndroid Build Coastguard Worker      GNError: Parse fails.
337*6777b538SAndroid Build Coastguard Worker    """
338*6777b538SAndroid Build Coastguard Worker    result = self._ParseAllowTrailing()
339*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
340*6777b538SAndroid Build Coastguard Worker    if not self.IsDone():
341*6777b538SAndroid Build Coastguard Worker      raise GNError("Trailing input after parsing:\n  " + self.input[self.cur:])
342*6777b538SAndroid Build Coastguard Worker    return result
343*6777b538SAndroid Build Coastguard Worker
344*6777b538SAndroid Build Coastguard Worker  def ParseArgs(self):
345*6777b538SAndroid Build Coastguard Worker    """Converts a whitespace-separated list of ident=literals to a dict.
346*6777b538SAndroid Build Coastguard Worker
347*6777b538SAndroid Build Coastguard Worker    See additional usage notes on FromGNArgs(), above.
348*6777b538SAndroid Build Coastguard Worker
349*6777b538SAndroid Build Coastguard Worker    Raises:
350*6777b538SAndroid Build Coastguard Worker      GNError: Parse fails.
351*6777b538SAndroid Build Coastguard Worker    """
352*6777b538SAndroid Build Coastguard Worker    d = {}
353*6777b538SAndroid Build Coastguard Worker
354*6777b538SAndroid Build Coastguard Worker    self.ReplaceImports()
355*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
356*6777b538SAndroid Build Coastguard Worker
357*6777b538SAndroid Build Coastguard Worker    while not self.IsDone():
358*6777b538SAndroid Build Coastguard Worker      ident = self._ParseIdent()
359*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
360*6777b538SAndroid Build Coastguard Worker      if self.input[self.cur] != '=':
361*6777b538SAndroid Build Coastguard Worker        raise GNError("Unexpected token: " + self.input[self.cur:])
362*6777b538SAndroid Build Coastguard Worker      self.cur += 1
363*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
364*6777b538SAndroid Build Coastguard Worker      val = self._ParseAllowTrailing()
365*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
366*6777b538SAndroid Build Coastguard Worker      d[ident] = val
367*6777b538SAndroid Build Coastguard Worker
368*6777b538SAndroid Build Coastguard Worker    return d
369*6777b538SAndroid Build Coastguard Worker
370*6777b538SAndroid Build Coastguard Worker  def _ParseAllowTrailing(self):
371*6777b538SAndroid Build Coastguard Worker    """Internal version of Parse() that doesn't check for trailing stuff."""
372*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
373*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
374*6777b538SAndroid Build Coastguard Worker      raise GNError("Expected input to parse.")
375*6777b538SAndroid Build Coastguard Worker
376*6777b538SAndroid Build Coastguard Worker    next_char = self.input[self.cur]
377*6777b538SAndroid Build Coastguard Worker    if next_char == '[':
378*6777b538SAndroid Build Coastguard Worker      return self.ParseList()
379*6777b538SAndroid Build Coastguard Worker    elif next_char == '{':
380*6777b538SAndroid Build Coastguard Worker      return self.ParseScope()
381*6777b538SAndroid Build Coastguard Worker    elif _IsDigitOrMinus(next_char):
382*6777b538SAndroid Build Coastguard Worker      return self.ParseNumber()
383*6777b538SAndroid Build Coastguard Worker    elif next_char == '"':
384*6777b538SAndroid Build Coastguard Worker      return self.ParseString()
385*6777b538SAndroid Build Coastguard Worker    elif self._ConstantFollows('true'):
386*6777b538SAndroid Build Coastguard Worker      return True
387*6777b538SAndroid Build Coastguard Worker    elif self._ConstantFollows('false'):
388*6777b538SAndroid Build Coastguard Worker      return False
389*6777b538SAndroid Build Coastguard Worker    else:
390*6777b538SAndroid Build Coastguard Worker      raise GNError("Unexpected token: " + self.input[self.cur:])
391*6777b538SAndroid Build Coastguard Worker
392*6777b538SAndroid Build Coastguard Worker  def _ParseIdent(self):
393*6777b538SAndroid Build Coastguard Worker    ident = ''
394*6777b538SAndroid Build Coastguard Worker
395*6777b538SAndroid Build Coastguard Worker    next_char = self.input[self.cur]
396*6777b538SAndroid Build Coastguard Worker    if not next_char.isalpha() and not next_char=='_':
397*6777b538SAndroid Build Coastguard Worker      raise GNError("Expected an identifier: " + self.input[self.cur:])
398*6777b538SAndroid Build Coastguard Worker
399*6777b538SAndroid Build Coastguard Worker    ident += next_char
400*6777b538SAndroid Build Coastguard Worker    self.cur += 1
401*6777b538SAndroid Build Coastguard Worker
402*6777b538SAndroid Build Coastguard Worker    next_char = self.input[self.cur]
403*6777b538SAndroid Build Coastguard Worker    while next_char.isalpha() or next_char.isdigit() or next_char=='_':
404*6777b538SAndroid Build Coastguard Worker      ident += next_char
405*6777b538SAndroid Build Coastguard Worker      self.cur += 1
406*6777b538SAndroid Build Coastguard Worker      next_char = self.input[self.cur]
407*6777b538SAndroid Build Coastguard Worker
408*6777b538SAndroid Build Coastguard Worker    return ident
409*6777b538SAndroid Build Coastguard Worker
410*6777b538SAndroid Build Coastguard Worker  def ParseNumber(self):
411*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
412*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
413*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected number but got nothing.')
414*6777b538SAndroid Build Coastguard Worker
415*6777b538SAndroid Build Coastguard Worker    begin = self.cur
416*6777b538SAndroid Build Coastguard Worker
417*6777b538SAndroid Build Coastguard Worker    # The first character can include a negative sign.
418*6777b538SAndroid Build Coastguard Worker    if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]):
419*6777b538SAndroid Build Coastguard Worker      self.cur += 1
420*6777b538SAndroid Build Coastguard Worker    while not self.IsDone() and self.input[self.cur].isdigit():
421*6777b538SAndroid Build Coastguard Worker      self.cur += 1
422*6777b538SAndroid Build Coastguard Worker
423*6777b538SAndroid Build Coastguard Worker    number_string = self.input[begin:self.cur]
424*6777b538SAndroid Build Coastguard Worker    if not len(number_string) or number_string == '-':
425*6777b538SAndroid Build Coastguard Worker      raise GNError('Not a valid number.')
426*6777b538SAndroid Build Coastguard Worker    return int(number_string)
427*6777b538SAndroid Build Coastguard Worker
428*6777b538SAndroid Build Coastguard Worker  def ParseString(self):
429*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
430*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
431*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected string but got nothing.')
432*6777b538SAndroid Build Coastguard Worker
433*6777b538SAndroid Build Coastguard Worker    if self.input[self.cur] != '"':
434*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected string beginning in a " but got:\n  ' +
435*6777b538SAndroid Build Coastguard Worker                    self.input[self.cur:])
436*6777b538SAndroid Build Coastguard Worker    self.cur += 1  # Skip over quote.
437*6777b538SAndroid Build Coastguard Worker
438*6777b538SAndroid Build Coastguard Worker    begin = self.cur
439*6777b538SAndroid Build Coastguard Worker    while not self.IsDone() and self.input[self.cur] != '"':
440*6777b538SAndroid Build Coastguard Worker      if self.input[self.cur] == '\\':
441*6777b538SAndroid Build Coastguard Worker        self.cur += 1  # Skip over the backslash.
442*6777b538SAndroid Build Coastguard Worker        if self.IsDone():
443*6777b538SAndroid Build Coastguard Worker          raise GNError('String ends in a backslash in:\n  ' + self.input)
444*6777b538SAndroid Build Coastguard Worker      self.cur += 1
445*6777b538SAndroid Build Coastguard Worker
446*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
447*6777b538SAndroid Build Coastguard Worker      raise GNError('Unterminated string:\n  ' + self.input[begin:])
448*6777b538SAndroid Build Coastguard Worker
449*6777b538SAndroid Build Coastguard Worker    end = self.cur
450*6777b538SAndroid Build Coastguard Worker    self.cur += 1  # Consume trailing ".
451*6777b538SAndroid Build Coastguard Worker
452*6777b538SAndroid Build Coastguard Worker    return UnescapeGNString(self.input[begin:end])
453*6777b538SAndroid Build Coastguard Worker
454*6777b538SAndroid Build Coastguard Worker  def ParseList(self):
455*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
456*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
457*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected list but got nothing.')
458*6777b538SAndroid Build Coastguard Worker
459*6777b538SAndroid Build Coastguard Worker    # Skip over opening '['.
460*6777b538SAndroid Build Coastguard Worker    if self.input[self.cur] != '[':
461*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected [ for list but got:\n  ' + self.input[self.cur:])
462*6777b538SAndroid Build Coastguard Worker    self.cur += 1
463*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
464*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
465*6777b538SAndroid Build Coastguard Worker      raise GNError('Unterminated list:\n  ' + self.input)
466*6777b538SAndroid Build Coastguard Worker
467*6777b538SAndroid Build Coastguard Worker    list_result = []
468*6777b538SAndroid Build Coastguard Worker    previous_had_trailing_comma = True
469*6777b538SAndroid Build Coastguard Worker    while not self.IsDone():
470*6777b538SAndroid Build Coastguard Worker      if self.input[self.cur] == ']':
471*6777b538SAndroid Build Coastguard Worker        self.cur += 1  # Skip over ']'.
472*6777b538SAndroid Build Coastguard Worker        return list_result
473*6777b538SAndroid Build Coastguard Worker
474*6777b538SAndroid Build Coastguard Worker      if not previous_had_trailing_comma:
475*6777b538SAndroid Build Coastguard Worker        raise GNError('List items not separated by comma.')
476*6777b538SAndroid Build Coastguard Worker
477*6777b538SAndroid Build Coastguard Worker      list_result += [ self._ParseAllowTrailing() ]
478*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
479*6777b538SAndroid Build Coastguard Worker      if self.IsDone():
480*6777b538SAndroid Build Coastguard Worker        break
481*6777b538SAndroid Build Coastguard Worker
482*6777b538SAndroid Build Coastguard Worker      # Consume comma if there is one.
483*6777b538SAndroid Build Coastguard Worker      previous_had_trailing_comma = self.input[self.cur] == ','
484*6777b538SAndroid Build Coastguard Worker      if previous_had_trailing_comma:
485*6777b538SAndroid Build Coastguard Worker        # Consume comma.
486*6777b538SAndroid Build Coastguard Worker        self.cur += 1
487*6777b538SAndroid Build Coastguard Worker        self.ConsumeCommentAndWhitespace()
488*6777b538SAndroid Build Coastguard Worker
489*6777b538SAndroid Build Coastguard Worker    raise GNError('Unterminated list:\n  ' + self.input)
490*6777b538SAndroid Build Coastguard Worker
491*6777b538SAndroid Build Coastguard Worker  def ParseScope(self):
492*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
493*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
494*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected scope but got nothing.')
495*6777b538SAndroid Build Coastguard Worker
496*6777b538SAndroid Build Coastguard Worker    # Skip over opening '{'.
497*6777b538SAndroid Build Coastguard Worker    if self.input[self.cur] != '{':
498*6777b538SAndroid Build Coastguard Worker      raise GNError('Expected { for scope but got:\n ' + self.input[self.cur:])
499*6777b538SAndroid Build Coastguard Worker    self.cur += 1
500*6777b538SAndroid Build Coastguard Worker    self.ConsumeCommentAndWhitespace()
501*6777b538SAndroid Build Coastguard Worker    if self.IsDone():
502*6777b538SAndroid Build Coastguard Worker      raise GNError('Unterminated scope:\n ' + self.input)
503*6777b538SAndroid Build Coastguard Worker
504*6777b538SAndroid Build Coastguard Worker    scope_result = {}
505*6777b538SAndroid Build Coastguard Worker    while not self.IsDone():
506*6777b538SAndroid Build Coastguard Worker      if self.input[self.cur] == '}':
507*6777b538SAndroid Build Coastguard Worker        self.cur += 1
508*6777b538SAndroid Build Coastguard Worker        return scope_result
509*6777b538SAndroid Build Coastguard Worker
510*6777b538SAndroid Build Coastguard Worker      ident = self._ParseIdent()
511*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
512*6777b538SAndroid Build Coastguard Worker      if self.input[self.cur] != '=':
513*6777b538SAndroid Build Coastguard Worker        raise GNError("Unexpected token: " + self.input[self.cur:])
514*6777b538SAndroid Build Coastguard Worker      self.cur += 1
515*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
516*6777b538SAndroid Build Coastguard Worker      val = self._ParseAllowTrailing()
517*6777b538SAndroid Build Coastguard Worker      self.ConsumeCommentAndWhitespace()
518*6777b538SAndroid Build Coastguard Worker      scope_result[ident] = val
519*6777b538SAndroid Build Coastguard Worker
520*6777b538SAndroid Build Coastguard Worker    raise GNError('Unterminated scope:\n ' + self.input)
521*6777b538SAndroid Build Coastguard Worker
522*6777b538SAndroid Build Coastguard Worker  def _ConstantFollows(self, constant):
523*6777b538SAndroid Build Coastguard Worker    """Checks and maybe consumes a string constant at current input location.
524*6777b538SAndroid Build Coastguard Worker
525*6777b538SAndroid Build Coastguard Worker    Param:
526*6777b538SAndroid Build Coastguard Worker      constant: The string constant to check.
527*6777b538SAndroid Build Coastguard Worker
528*6777b538SAndroid Build Coastguard Worker    Returns:
529*6777b538SAndroid Build Coastguard Worker      True if |constant| follows immediately at the current location in the
530*6777b538SAndroid Build Coastguard Worker      input. In this case, the string is consumed as a side effect. Otherwise,
531*6777b538SAndroid Build Coastguard Worker      returns False and the current position is unchanged.
532*6777b538SAndroid Build Coastguard Worker    """
533*6777b538SAndroid Build Coastguard Worker    end = self.cur + len(constant)
534*6777b538SAndroid Build Coastguard Worker    if end > len(self.input):
535*6777b538SAndroid Build Coastguard Worker      return False  # Not enough room.
536*6777b538SAndroid Build Coastguard Worker    if self.input[self.cur:end] == constant:
537*6777b538SAndroid Build Coastguard Worker      self.cur = end
538*6777b538SAndroid Build Coastguard Worker      return True
539*6777b538SAndroid Build Coastguard Worker    return False
540*6777b538SAndroid Build Coastguard Worker
541*6777b538SAndroid Build Coastguard Worker
542*6777b538SAndroid Build Coastguard Workerdef ReadBuildVars(output_directory):
543*6777b538SAndroid Build Coastguard Worker  """Parses $output_directory/build_vars.json into a dict."""
544*6777b538SAndroid Build Coastguard Worker  with open(os.path.join(output_directory, BUILD_VARS_FILENAME)) as f:
545*6777b538SAndroid Build Coastguard Worker    return json.load(f)
546*6777b538SAndroid Build Coastguard Worker
547*6777b538SAndroid Build Coastguard Worker
548*6777b538SAndroid Build Coastguard Workerdef CreateBuildCommand(output_directory):
549*6777b538SAndroid Build Coastguard Worker  """Returns [cmd, -C, output_directory].
550*6777b538SAndroid Build Coastguard Worker
551*6777b538SAndroid Build Coastguard Worker  Where |cmd| is one of: siso ninja, ninja, or autoninja.
552*6777b538SAndroid Build Coastguard Worker  """
553*6777b538SAndroid Build Coastguard Worker  suffix = '.bat' if sys.platform.startswith('win32') else ''
554*6777b538SAndroid Build Coastguard Worker  # Prefer the version on PATH, but fallback to known version if PATH doesn't
555*6777b538SAndroid Build Coastguard Worker  # have one (e.g. on bots).
556*6777b538SAndroid Build Coastguard Worker  if not shutil.which(f'autoninja{suffix}'):
557*6777b538SAndroid Build Coastguard Worker    third_party_prefix = os.path.join(_CHROMIUM_ROOT, 'third_party')
558*6777b538SAndroid Build Coastguard Worker    ninja_prefix = os.path.join(third_party_prefix, 'ninja', '')
559*6777b538SAndroid Build Coastguard Worker    siso_prefix = os.path.join(third_party_prefix, 'siso', '')
560*6777b538SAndroid Build Coastguard Worker    # Also - bots configure reclient manually, and so do not use the "auto"
561*6777b538SAndroid Build Coastguard Worker    # wrappers.
562*6777b538SAndroid Build Coastguard Worker    ninja_cmd = [f'{ninja_prefix}ninja{suffix}']
563*6777b538SAndroid Build Coastguard Worker    siso_cmd = [f'{siso_prefix}siso{suffix}', 'ninja']
564*6777b538SAndroid Build Coastguard Worker  else:
565*6777b538SAndroid Build Coastguard Worker    ninja_cmd = [f'autoninja{suffix}']
566*6777b538SAndroid Build Coastguard Worker    siso_cmd = list(ninja_cmd)
567*6777b538SAndroid Build Coastguard Worker
568*6777b538SAndroid Build Coastguard Worker  if output_directory and os.path.relpath(output_directory) != '.':
569*6777b538SAndroid Build Coastguard Worker    ninja_cmd += ['-C', output_directory]
570*6777b538SAndroid Build Coastguard Worker    siso_cmd += ['-C', output_directory]
571*6777b538SAndroid Build Coastguard Worker  siso_deps = os.path.exists(os.path.join(output_directory, '.siso_deps'))
572*6777b538SAndroid Build Coastguard Worker  ninja_deps = os.path.exists(os.path.join(output_directory, '.ninja_deps'))
573*6777b538SAndroid Build Coastguard Worker  if siso_deps and ninja_deps:
574*6777b538SAndroid Build Coastguard Worker    raise Exception('Found both .siso_deps and .ninja_deps in '
575*6777b538SAndroid Build Coastguard Worker                    f'{output_directory}. Not sure which build tool to use. '
576*6777b538SAndroid Build Coastguard Worker                    'Please delete one, or better, run "gn clean".')
577*6777b538SAndroid Build Coastguard Worker  if siso_deps:
578*6777b538SAndroid Build Coastguard Worker    return siso_cmd
579*6777b538SAndroid Build Coastguard Worker  return ninja_cmd
580