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