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