[M73 Dev][EFL] Fix errors to generate ninja files
[platform/framework/web/chromium-efl.git] / build / gn_helpers.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Helper functions useful when writing scripts that integrate with GN.
6
7 The main functions are ToGNString and FromGNString which convert between
8 serialized GN veriables and Python variables.
9
10 To use in a random python file in the build:
11
12   import os
13   import sys
14
15   sys.path.append(os.path.join(os.path.dirname(__file__),
16                                os.pardir, os.pardir, "build"))
17   import gn_helpers
18
19 Where the sequence of parameters to join is the relative path from your source
20 file to the build directory."""
21
22 class GNException(Exception):
23   pass
24
25
26 def ToGNString(value, allow_dicts = True):
27   """Returns a stringified GN equivalent of the Python value.
28
29   allow_dicts indicates if this function will allow converting dictionaries
30   to GN scopes. This is only possible at the top level, you can't nest a
31   GN scope in a list, so this should be set to False for recursive calls."""
32   if isinstance(value, basestring):
33     if value.find('\n') >= 0:
34       raise GNException("Trying to print a string with a newline in it.")
35     return '"' + \
36         value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
37         '"'
38
39   if isinstance(value, unicode):
40     return ToGNString(value.encode('utf-8'))
41
42   if isinstance(value, bool):
43     if value:
44       return "true"
45     return "false"
46
47   if isinstance(value, list):
48     return '[ %s ]' % ', '.join(ToGNString(v) for v in value)
49
50   if isinstance(value, dict):
51     if not allow_dicts:
52       raise GNException("Attempting to recursively print a dictionary.")
53     result = ""
54     for key in sorted(value):
55       if not isinstance(key, basestring):
56         raise GNException("Dictionary key is not a string.")
57       result += "%s = %s\n" % (key, ToGNString(value[key], False))
58     return result
59
60   if isinstance(value, int):
61     return str(value)
62
63   raise GNException("Unsupported type when printing to GN.")
64
65
66 def FromGNString(input_string):
67   """Converts the input string from a GN serialized value to Python values.
68
69   For details on supported types see GNValueParser.Parse() below.
70
71   If your GN script did:
72     something = [ "file1", "file2" ]
73     args = [ "--values=$something" ]
74   The command line would look something like:
75     --values="[ \"file1\", \"file2\" ]"
76   Which when interpreted as a command line gives the value:
77     [ "file1", "file2" ]
78
79   You can parse this into a Python list using GN rules with:
80     input_values = FromGNValues(options.values)
81   Although the Python 'ast' module will parse many forms of such input, it
82   will not handle GN escaping properly, nor GN booleans. You should use this
83   function instead.
84
85
86   A NOTE ON STRING HANDLING:
87
88   If you just pass a string on the command line to your Python script, or use
89   string interpolation on a string variable, the strings will not be quoted:
90     str = "asdf"
91     args = [ str, "--value=$str" ]
92   Will yield the command line:
93     asdf --value=asdf
94   The unquoted asdf string will not be valid input to this function, which
95   accepts only quoted strings like GN scripts. In such cases, you can just use
96   the Python string literal directly.
97
98   The main use cases for this is for other types, in particular lists. When
99   using string interpolation on a list (as in the top example) the embedded
100   strings will be quoted and escaped according to GN rules so the list can be
101   re-parsed to get the same result."""
102   parser = GNValueParser(input_string)
103   return parser.Parse()
104
105
106 def FromGNArgs(input_string):
107   """Converts a string with a bunch of gn arg assignments into a Python dict.
108
109   Given a whitespace-separated list of
110
111     <ident> = (integer | string | boolean | <list of the former>)
112
113   gn assignments, this returns a Python dict, i.e.:
114
115     FromGNArgs("foo=true\nbar=1\n") -> { 'foo': True, 'bar': 1 }.
116
117   Only simple types and lists supported; variables, structs, calls
118   and other, more complicated things are not.
119
120   This routine is meant to handle only the simple sorts of values that
121   arise in parsing --args.
122   """
123   parser = GNValueParser(input_string)
124   return parser.ParseArgs()
125
126
127 def UnescapeGNString(value):
128   """Given a string with GN escaping, returns the unescaped string.
129
130   Be careful not to feed with input from a Python parsing function like
131   'ast' because it will do Python unescaping, which will be incorrect when
132   fed into the GN unescaper."""
133   result = ''
134   i = 0
135   while i < len(value):
136     if value[i] == '\\':
137       if i < len(value) - 1:
138         next_char = value[i + 1]
139         if next_char in ('$', '"', '\\'):
140           # These are the escaped characters GN supports.
141           result += next_char
142           i += 1
143         else:
144           # Any other backslash is a literal.
145           result += '\\'
146     else:
147       result += value[i]
148     i += 1
149   return result
150
151
152 def _IsDigitOrMinus(char):
153   return char in "-0123456789"
154
155
156 class GNValueParser(object):
157   """Duplicates GN parsing of values and converts to Python types.
158
159   Normally you would use the wrapper function FromGNValue() below.
160
161   If you expect input as a specific type, you can also call one of the Parse*
162   functions directly. All functions throw GNException on invalid input. """
163   def __init__(self, string):
164     self.input = string
165     self.cur = 0
166
167   def IsDone(self):
168     return self.cur == len(self.input)
169
170   def ConsumeWhitespace(self):
171     while not self.IsDone() and self.input[self.cur] in ' \t\n':
172       self.cur += 1
173
174   def ConsumeComment(self):
175     if self.IsDone() or self.input[self.cur] != '#':
176       return
177
178     # Consume each comment, line by line.
179     while not self.IsDone() and self.input[self.cur] == '#':
180       # Consume the rest of the comment, up until the end of the line.
181       while not self.IsDone() and self.input[self.cur] != '\n':
182         self.cur += 1
183       # Move the cursor to the next line (if there is one).
184       if not self.IsDone():
185         self.cur += 1
186
187   def Parse(self):
188     """Converts a string representing a printed GN value to the Python type.
189
190     See additional usage notes on FromGNString above.
191
192     - GN booleans ('true', 'false') will be converted to Python booleans.
193
194     - GN numbers ('123') will be converted to Python numbers.
195
196     - GN strings (double-quoted as in '"asdf"') will be converted to Python
197       strings with GN escaping rules. GN string interpolation (embedded
198       variables preceded by $) are not supported and will be returned as
199       literals.
200
201     - GN lists ('[1, "asdf", 3]') will be converted to Python lists.
202
203     - GN scopes ('{ ... }') are not supported."""
204     result = self._ParseAllowTrailing()
205     self.ConsumeWhitespace()
206     if not self.IsDone():
207       raise GNException("Trailing input after parsing:\n  " +
208                         self.input[self.cur:])
209     return result
210
211   def ParseArgs(self):
212     """Converts a whitespace-separated list of ident=literals to a dict.
213
214     See additional usage notes on FromGNArgs, above.
215     """
216     d = {}
217
218     self.ConsumeWhitespace()
219     self.ConsumeComment()
220     while not self.IsDone():
221       ident = self._ParseIdent()
222       self.ConsumeWhitespace()
223       if self.input[self.cur] != '=':
224         raise GNException("Unexpected token: " + self.input[self.cur:])
225       self.cur += 1
226       self.ConsumeWhitespace()
227       val = self._ParseAllowTrailing()
228       self.ConsumeWhitespace()
229       self.ConsumeComment()
230       d[ident] = val
231
232     return d
233
234   def _ParseAllowTrailing(self):
235     """Internal version of Parse that doesn't check for trailing stuff."""
236     self.ConsumeWhitespace()
237     if self.IsDone():
238       raise GNException("Expected input to parse.")
239
240     next_char = self.input[self.cur]
241     if next_char == '[':
242       return self.ParseList()
243     elif _IsDigitOrMinus(next_char):
244       return self.ParseNumber()
245     elif next_char == '"':
246       return self.ParseString()
247     elif self._ConstantFollows('true'):
248       return True
249     elif self._ConstantFollows('false'):
250       return False
251     else:
252       raise GNException("Unexpected token: " + self.input[self.cur:])
253
254   def _ParseIdent(self):
255     ident = ''
256
257     next_char = self.input[self.cur]
258     if not next_char.isalpha() and not next_char=='_':
259       raise GNException("Expected an identifier: " + self.input[self.cur:])
260
261     ident += next_char
262     self.cur += 1
263
264     next_char = self.input[self.cur]
265     while next_char.isalpha() or next_char.isdigit() or next_char=='_':
266       ident += next_char
267       self.cur += 1
268       next_char = self.input[self.cur]
269
270     return ident
271
272   def ParseNumber(self):
273     self.ConsumeWhitespace()
274     if self.IsDone():
275       raise GNException('Expected number but got nothing.')
276
277     begin = self.cur
278
279     # The first character can include a negative sign.
280     if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]):
281       self.cur += 1
282     while not self.IsDone() and self.input[self.cur].isdigit():
283       self.cur += 1
284
285     number_string = self.input[begin:self.cur]
286     if not len(number_string) or number_string == '-':
287       raise GNException("Not a valid number.")
288     return int(number_string)
289
290   def ParseString(self):
291     self.ConsumeWhitespace()
292     if self.IsDone():
293       raise GNException('Expected string but got nothing.')
294
295     if self.input[self.cur] != '"':
296       raise GNException('Expected string beginning in a " but got:\n  ' +
297                         self.input[self.cur:])
298     self.cur += 1  # Skip over quote.
299
300     begin = self.cur
301     while not self.IsDone() and self.input[self.cur] != '"':
302       if self.input[self.cur] == '\\':
303         self.cur += 1  # Skip over the backslash.
304         if self.IsDone():
305           raise GNException("String ends in a backslash in:\n  " +
306                             self.input)
307       self.cur += 1
308
309     if self.IsDone():
310       raise GNException('Unterminated string:\n  ' + self.input[begin:])
311
312     end = self.cur
313     self.cur += 1  # Consume trailing ".
314
315     return UnescapeGNString(self.input[begin:end])
316
317   def ParseList(self):
318     self.ConsumeWhitespace()
319     if self.IsDone():
320       raise GNException('Expected list but got nothing.')
321
322     # Skip over opening '['.
323     if self.input[self.cur] != '[':
324       raise GNException("Expected [ for list but got:\n  " +
325                         self.input[self.cur:])
326     self.cur += 1
327     self.ConsumeWhitespace()
328     if self.IsDone():
329       raise GNException("Unterminated list:\n  " + self.input)
330
331     list_result = []
332     previous_had_trailing_comma = True
333     while not self.IsDone():
334       if self.input[self.cur] == ']':
335         self.cur += 1  # Skip over ']'.
336         return list_result
337
338       if not previous_had_trailing_comma:
339         raise GNException("List items not separated by comma.")
340
341       list_result += [ self._ParseAllowTrailing() ]
342       self.ConsumeWhitespace()
343       if self.IsDone():
344         break
345
346       # Consume comma if there is one.
347       previous_had_trailing_comma = self.input[self.cur] == ','
348       if previous_had_trailing_comma:
349         # Consume comma.
350         self.cur += 1
351         self.ConsumeWhitespace()
352
353     raise GNException("Unterminated list:\n  " + self.input)
354
355   def _ConstantFollows(self, constant):
356     """Returns true if the given constant follows immediately at the current
357     location in the input. If it does, the text is consumed and the function
358     returns true. Otherwise, returns false and the current position is
359     unchanged."""
360     end = self.cur + len(constant)
361     if end > len(self.input):
362       return False  # Not enough room.
363     if self.input[self.cur:end] == constant:
364       self.cur = end
365       return True
366     return False