1*635a8641SAndroid Build Coastguard Worker# Copyright 2014 The Chromium Authors. All rights reserved. 2*635a8641SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*635a8641SAndroid Build Coastguard Worker# found in the LICENSE file. 4*635a8641SAndroid Build Coastguard Worker 5*635a8641SAndroid Build Coastguard Worker"""Converts a given gypi file to a python scope and writes the result to stdout. 6*635a8641SAndroid Build Coastguard Worker 7*635a8641SAndroid Build Coastguard WorkerUSING THIS SCRIPT IN CHROMIUM 8*635a8641SAndroid Build Coastguard Worker 9*635a8641SAndroid Build Coastguard WorkerForking Python to run this script in the middle of GN is slow, especially on 10*635a8641SAndroid Build Coastguard WorkerWindows, and it makes both the GYP and GN files harder to follow. You can't 11*635a8641SAndroid Build Coastguard Workeruse "git grep" to find files in the GN build any more, and tracking everything 12*635a8641SAndroid Build Coastguard Workerin GYP down requires a level of indirection. Any calls will have to be removed 13*635a8641SAndroid Build Coastguard Workerand cleaned up once the GYP-to-GN transition is complete. 14*635a8641SAndroid Build Coastguard Worker 15*635a8641SAndroid Build Coastguard WorkerAs a result, we only use this script when the list of files is large and 16*635a8641SAndroid Build Coastguard Workerfrequently-changing. In these cases, having one canonical list outweights the 17*635a8641SAndroid Build Coastguard Workerdownsides. 18*635a8641SAndroid Build Coastguard Worker 19*635a8641SAndroid Build Coastguard WorkerAs of this writing, the GN build is basically complete. It's likely that all 20*635a8641SAndroid Build Coastguard Workerlarge and frequently changing targets where this is appropriate use this 21*635a8641SAndroid Build Coastguard Workermechanism already. And since we hope to turn down the GYP build soon, the time 22*635a8641SAndroid Build Coastguard Workerhorizon is also relatively short. As a result, it is likely that no additional 23*635a8641SAndroid Build Coastguard Workeruses of this script should every be added to the build. During this later part 24*635a8641SAndroid Build Coastguard Workerof the transition period, we should be focusing more and more on the absolute 25*635a8641SAndroid Build Coastguard Workerreadability of the GN build. 26*635a8641SAndroid Build Coastguard Worker 27*635a8641SAndroid Build Coastguard Worker 28*635a8641SAndroid Build Coastguard WorkerHOW TO USE 29*635a8641SAndroid Build Coastguard Worker 30*635a8641SAndroid Build Coastguard WorkerIt is assumed that the file contains a toplevel dictionary, and this script 31*635a8641SAndroid Build Coastguard Workerwill return that dictionary as a GN "scope" (see example below). This script 32*635a8641SAndroid Build Coastguard Workerdoes not know anything about GYP and it will not expand variables or execute 33*635a8641SAndroid Build Coastguard Workerconditions. 34*635a8641SAndroid Build Coastguard Worker 35*635a8641SAndroid Build Coastguard WorkerIt will strip conditions blocks. 36*635a8641SAndroid Build Coastguard Worker 37*635a8641SAndroid Build Coastguard WorkerA variables block at the top level will be flattened so that the variables 38*635a8641SAndroid Build Coastguard Workerappear in the root dictionary. This way they can be returned to the GN code. 39*635a8641SAndroid Build Coastguard Worker 40*635a8641SAndroid Build Coastguard WorkerSay your_file.gypi looked like this: 41*635a8641SAndroid Build Coastguard Worker { 42*635a8641SAndroid Build Coastguard Worker 'sources': [ 'a.cc', 'b.cc' ], 43*635a8641SAndroid Build Coastguard Worker 'defines': [ 'ENABLE_DOOM_MELON' ], 44*635a8641SAndroid Build Coastguard Worker } 45*635a8641SAndroid Build Coastguard Worker 46*635a8641SAndroid Build Coastguard WorkerYou would call it like this: 47*635a8641SAndroid Build Coastguard Worker gypi_values = exec_script("//build/gypi_to_gn.py", 48*635a8641SAndroid Build Coastguard Worker [ rebase_path("your_file.gypi") ], 49*635a8641SAndroid Build Coastguard Worker "scope", 50*635a8641SAndroid Build Coastguard Worker [ "your_file.gypi" ]) 51*635a8641SAndroid Build Coastguard Worker 52*635a8641SAndroid Build Coastguard WorkerNotes: 53*635a8641SAndroid Build Coastguard Worker - The rebase_path call converts the gypi file from being relative to the 54*635a8641SAndroid Build Coastguard Worker current build file to being system absolute for calling the script, which 55*635a8641SAndroid Build Coastguard Worker will have a different current directory than this file. 56*635a8641SAndroid Build Coastguard Worker 57*635a8641SAndroid Build Coastguard Worker - The "scope" parameter tells GN to interpret the result as a series of GN 58*635a8641SAndroid Build Coastguard Worker variable assignments. 59*635a8641SAndroid Build Coastguard Worker 60*635a8641SAndroid Build Coastguard Worker - The last file argument to exec_script tells GN that the given file is a 61*635a8641SAndroid Build Coastguard Worker dependency of the build so Ninja can automatically re-run GN if the file 62*635a8641SAndroid Build Coastguard Worker changes. 63*635a8641SAndroid Build Coastguard Worker 64*635a8641SAndroid Build Coastguard WorkerRead the values into a target like this: 65*635a8641SAndroid Build Coastguard Worker component("mycomponent") { 66*635a8641SAndroid Build Coastguard Worker sources = gypi_values.sources 67*635a8641SAndroid Build Coastguard Worker defines = gypi_values.defines 68*635a8641SAndroid Build Coastguard Worker } 69*635a8641SAndroid Build Coastguard Worker 70*635a8641SAndroid Build Coastguard WorkerSometimes your .gypi file will include paths relative to a different 71*635a8641SAndroid Build Coastguard Workerdirectory than the current .gn file. In this case, you can rebase them to 72*635a8641SAndroid Build Coastguard Workerbe relative to the current directory. 73*635a8641SAndroid Build Coastguard Worker sources = rebase_path(gypi_values.sources, ".", 74*635a8641SAndroid Build Coastguard Worker "//path/gypi/input/values/are/relative/to") 75*635a8641SAndroid Build Coastguard Worker 76*635a8641SAndroid Build Coastguard WorkerThis script will tolerate a 'variables' in the toplevel dictionary or not. If 77*635a8641SAndroid Build Coastguard Workerthe toplevel dictionary just contains one item called 'variables', it will be 78*635a8641SAndroid Build Coastguard Workercollapsed away and the result will be the contents of that dictinoary. Some 79*635a8641SAndroid Build Coastguard Worker.gypi files are written with or without this, depending on how they expect to 80*635a8641SAndroid Build Coastguard Workerbe embedded into a .gyp file. 81*635a8641SAndroid Build Coastguard Worker 82*635a8641SAndroid Build Coastguard WorkerThis script also has the ability to replace certain substrings in the input. 83*635a8641SAndroid Build Coastguard WorkerGenerally this is used to emulate GYP variable expansion. If you passed the 84*635a8641SAndroid Build Coastguard Workerargument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in 85*635a8641SAndroid Build Coastguard Workerthe input will be replaced with "bar": 86*635a8641SAndroid Build Coastguard Worker 87*635a8641SAndroid Build Coastguard Worker gypi_values = exec_script("//build/gypi_to_gn.py", 88*635a8641SAndroid Build Coastguard Worker [ rebase_path("your_file.gypi"), 89*635a8641SAndroid Build Coastguard Worker "--replace=<(foo)=bar"], 90*635a8641SAndroid Build Coastguard Worker "scope", 91*635a8641SAndroid Build Coastguard Worker [ "your_file.gypi" ]) 92*635a8641SAndroid Build Coastguard Worker 93*635a8641SAndroid Build Coastguard Worker""" 94*635a8641SAndroid Build Coastguard Worker 95*635a8641SAndroid Build Coastguard Workerimport gn_helpers 96*635a8641SAndroid Build Coastguard Workerfrom optparse import OptionParser 97*635a8641SAndroid Build Coastguard Workerimport sys 98*635a8641SAndroid Build Coastguard Worker 99*635a8641SAndroid Build Coastguard Workerdef LoadPythonDictionary(path): 100*635a8641SAndroid Build Coastguard Worker file_string = open(path).read() 101*635a8641SAndroid Build Coastguard Worker try: 102*635a8641SAndroid Build Coastguard Worker file_data = eval(file_string, {'__builtins__': None}, None) 103*635a8641SAndroid Build Coastguard Worker except SyntaxError as e: 104*635a8641SAndroid Build Coastguard Worker e.filename = path 105*635a8641SAndroid Build Coastguard Worker raise 106*635a8641SAndroid Build Coastguard Worker except Exception as e: 107*635a8641SAndroid Build Coastguard Worker raise Exception("Unexpected error while reading %s: %s" % (path, str(e))) 108*635a8641SAndroid Build Coastguard Worker 109*635a8641SAndroid Build Coastguard Worker assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path 110*635a8641SAndroid Build Coastguard Worker 111*635a8641SAndroid Build Coastguard Worker # Flatten any variables to the top level. 112*635a8641SAndroid Build Coastguard Worker if 'variables' in file_data: 113*635a8641SAndroid Build Coastguard Worker file_data.update(file_data['variables']) 114*635a8641SAndroid Build Coastguard Worker del file_data['variables'] 115*635a8641SAndroid Build Coastguard Worker 116*635a8641SAndroid Build Coastguard Worker # Strip all elements that this script can't process. 117*635a8641SAndroid Build Coastguard Worker elements_to_strip = [ 118*635a8641SAndroid Build Coastguard Worker 'conditions', 119*635a8641SAndroid Build Coastguard Worker 'target_conditions', 120*635a8641SAndroid Build Coastguard Worker 'target_defaults', 121*635a8641SAndroid Build Coastguard Worker 'targets', 122*635a8641SAndroid Build Coastguard Worker 'includes', 123*635a8641SAndroid Build Coastguard Worker 'actions', 124*635a8641SAndroid Build Coastguard Worker ] 125*635a8641SAndroid Build Coastguard Worker for element in elements_to_strip: 126*635a8641SAndroid Build Coastguard Worker if element in file_data: 127*635a8641SAndroid Build Coastguard Worker del file_data[element] 128*635a8641SAndroid Build Coastguard Worker 129*635a8641SAndroid Build Coastguard Worker return file_data 130*635a8641SAndroid Build Coastguard Worker 131*635a8641SAndroid Build Coastguard Worker 132*635a8641SAndroid Build Coastguard Workerdef ReplaceSubstrings(values, search_for, replace_with): 133*635a8641SAndroid Build Coastguard Worker """Recursively replaces substrings in a value. 134*635a8641SAndroid Build Coastguard Worker 135*635a8641SAndroid Build Coastguard Worker Replaces all substrings of the "search_for" with "repace_with" for all 136*635a8641SAndroid Build Coastguard Worker strings occurring in "values". This is done by recursively iterating into 137*635a8641SAndroid Build Coastguard Worker lists as well as the keys and values of dictionaries.""" 138*635a8641SAndroid Build Coastguard Worker if isinstance(values, str): 139*635a8641SAndroid Build Coastguard Worker return values.replace(search_for, replace_with) 140*635a8641SAndroid Build Coastguard Worker 141*635a8641SAndroid Build Coastguard Worker if isinstance(values, list): 142*635a8641SAndroid Build Coastguard Worker return [ReplaceSubstrings(v, search_for, replace_with) for v in values] 143*635a8641SAndroid Build Coastguard Worker 144*635a8641SAndroid Build Coastguard Worker if isinstance(values, dict): 145*635a8641SAndroid Build Coastguard Worker # For dictionaries, do the search for both the key and values. 146*635a8641SAndroid Build Coastguard Worker result = {} 147*635a8641SAndroid Build Coastguard Worker for key, value in values.items(): 148*635a8641SAndroid Build Coastguard Worker new_key = ReplaceSubstrings(key, search_for, replace_with) 149*635a8641SAndroid Build Coastguard Worker new_value = ReplaceSubstrings(value, search_for, replace_with) 150*635a8641SAndroid Build Coastguard Worker result[new_key] = new_value 151*635a8641SAndroid Build Coastguard Worker return result 152*635a8641SAndroid Build Coastguard Worker 153*635a8641SAndroid Build Coastguard Worker # Assume everything else is unchanged. 154*635a8641SAndroid Build Coastguard Worker return values 155*635a8641SAndroid Build Coastguard Worker 156*635a8641SAndroid Build Coastguard Workerdef main(): 157*635a8641SAndroid Build Coastguard Worker parser = OptionParser() 158*635a8641SAndroid Build Coastguard Worker parser.add_option("-r", "--replace", action="append", 159*635a8641SAndroid Build Coastguard Worker help="Replaces substrings. If passed a=b, replaces all substrs a with b.") 160*635a8641SAndroid Build Coastguard Worker (options, args) = parser.parse_args() 161*635a8641SAndroid Build Coastguard Worker 162*635a8641SAndroid Build Coastguard Worker if len(args) != 1: 163*635a8641SAndroid Build Coastguard Worker raise Exception("Need one argument which is the .gypi file to read.") 164*635a8641SAndroid Build Coastguard Worker 165*635a8641SAndroid Build Coastguard Worker data = LoadPythonDictionary(args[0]) 166*635a8641SAndroid Build Coastguard Worker if options.replace: 167*635a8641SAndroid Build Coastguard Worker # Do replacements for all specified patterns. 168*635a8641SAndroid Build Coastguard Worker for replace in options.replace: 169*635a8641SAndroid Build Coastguard Worker split = replace.split('=') 170*635a8641SAndroid Build Coastguard Worker # Allow "foo=" to replace with nothing. 171*635a8641SAndroid Build Coastguard Worker if len(split) == 1: 172*635a8641SAndroid Build Coastguard Worker split.append('') 173*635a8641SAndroid Build Coastguard Worker assert len(split) == 2, "Replacement must be of the form 'key=value'." 174*635a8641SAndroid Build Coastguard Worker data = ReplaceSubstrings(data, split[0], split[1]) 175*635a8641SAndroid Build Coastguard Worker 176*635a8641SAndroid Build Coastguard Worker # Sometimes .gypi files use the GYP syntax with percents at the end of the 177*635a8641SAndroid Build Coastguard Worker # variable name (to indicate not to overwrite a previously-defined value): 178*635a8641SAndroid Build Coastguard Worker # 'foo%': 'bar', 179*635a8641SAndroid Build Coastguard Worker # Convert these to regular variables. 180*635a8641SAndroid Build Coastguard Worker for key in data: 181*635a8641SAndroid Build Coastguard Worker if len(key) > 1 and key[len(key) - 1] == '%': 182*635a8641SAndroid Build Coastguard Worker data[key[:-1]] = data[key] 183*635a8641SAndroid Build Coastguard Worker del data[key] 184*635a8641SAndroid Build Coastguard Worker 185*635a8641SAndroid Build Coastguard Worker print(gn_helpers.ToGNString(data)) 186*635a8641SAndroid Build Coastguard Worker 187*635a8641SAndroid Build Coastguard Workerif __name__ == '__main__': 188*635a8641SAndroid Build Coastguard Worker try: 189*635a8641SAndroid Build Coastguard Worker main() 190*635a8641SAndroid Build Coastguard Worker except Exception as e: 191*635a8641SAndroid Build Coastguard Worker print(str(e)) 192*635a8641SAndroid Build Coastguard Worker sys.exit(1) 193