TabStripModel::GetActiveWebContents() might return NULL, and will cause a crash if...
[chromium-blink-merge.git] / build / gypi_to_gn.py
blobd52e3a0b3fdcf5b2e8149a8056b5a4751fbf6f40
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Converts a given gypi file to a python scope and writes the result to stdout.
7 It is assumed that the file contains a toplevel dictionary, and this script
8 will return that dictionary as a GN "scope" (see example below). This script
9 does not know anything about GYP and it will not expand variables or execute
10 conditions.
12 It will strip conditions blocks.
14 A variables block at the top level will be flattened so that the variables
15 appear in the root dictionary. This way they can be returned to the GN code.
17 Say your_file.gypi looked like this:
19 'sources': [ 'a.cc', 'b.cc' ],
20 'defines': [ 'ENABLE_DOOM_MELON' ],
23 You would call it like this:
24 gypi_values = exec_script("//build/gypi_to_gn.py",
25 [ rebase_path("your_file.gypi") ],
26 "scope",
27 [ "your_file.gypi" ])
29 Notes:
30 - The rebase_path call converts the gypi file from being relative to the
31 current build file to being system absolute for calling the script, which
32 will have a different current directory than this file.
34 - The "scope" parameter tells GN to interpret the result as a series of GN
35 variable assignments.
37 - The last file argument to exec_script tells GN that the given file is a
38 dependency of the build so Ninja can automatically re-run GN if the file
39 changes.
41 Read the values into a target like this:
42 component("mycomponent") {
43 sources = gypi_values.sources
44 defines = gypi_values.defines
47 Sometimes your .gypi file will include paths relative to a different
48 directory than the current .gn file. In this case, you can rebase them to
49 be relative to the current directory.
50 sources = rebase_path(gypi_values.sources, ".",
51 "//path/gypi/input/values/are/relative/to")
53 This script will tolerate a 'variables' in the toplevel dictionary or not. If
54 the toplevel dictionary just contains one item called 'variables', it will be
55 collapsed away and the result will be the contents of that dictinoary. Some
56 .gypi files are written with or without this, depending on how they expect to
57 be embedded into a .gyp file.
59 This script also has the ability to replace certain substrings in the input.
60 Generally this is used to emulate GYP variable expansion. If you passed the
61 argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
62 the input will be replaced with "bar":
64 gypi_values = exec_script("//build/gypi_to_gn.py",
65 [ rebase_path("your_file.gypi"),
66 "--replace=<(foo)=bar"],
67 "scope",
68 [ "your_file.gypi" ])
70 """
72 import gn_helpers
73 from optparse import OptionParser
74 import sys
76 def LoadPythonDictionary(path):
77 file_string = open(path).read()
78 try:
79 file_data = eval(file_string, {'__builtins__': None}, None)
80 except SyntaxError, e:
81 e.filename = path
82 raise
83 except Exception, e:
84 raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
86 assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
88 # Flatten any variables to the top level.
89 if 'variables' in file_data:
90 file_data.update(file_data['variables'])
91 del file_data['variables']
93 # Strip any conditions.
94 if 'conditions' in file_data:
95 del file_data['conditions']
96 if 'target_conditions' in file_data:
97 del file_data['target_conditions']
99 return file_data
102 def ReplaceSubstrings(values, search_for, replace_with):
103 """Recursively replaces substrings in a value.
105 Replaces all substrings of the "search_for" with "repace_with" for all
106 strings occurring in "values". This is done by recursively iterating into
107 lists as well as the keys and values of dictionaries."""
108 if isinstance(values, str):
109 return values.replace(search_for, replace_with)
111 if isinstance(values, list):
112 return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
114 if isinstance(values, dict):
115 # For dictionaries, do the search for both the key and values.
116 result = {}
117 for key, value in values.items():
118 new_key = ReplaceSubstrings(key, search_for, replace_with)
119 new_value = ReplaceSubstrings(value, search_for, replace_with)
120 result[new_key] = new_value
121 return result
123 # Assume everything else is unchanged.
124 return values
126 def main():
127 parser = OptionParser()
128 parser.add_option("-r", "--replace", action="append",
129 help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
130 (options, args) = parser.parse_args()
132 if len(args) != 1:
133 raise Exception("Need one argument which is the .gypi file to read.")
135 data = LoadPythonDictionary(args[0])
136 if options.replace:
137 # Do replacements for all specified patterns.
138 for replace in options.replace:
139 split = replace.split('=')
140 # Allow "foo=" to replace with nothing.
141 if len(split) == 1:
142 split.append('')
143 assert len(split) == 2, "Replacement must be of the form 'key=value'."
144 data = ReplaceSubstrings(data, split[0], split[1])
146 # Sometimes .gypi files use the GYP syntax with percents at the end of the
147 # variable name (to indicate not to overwrite a previously-defined value):
148 # 'foo%': 'bar',
149 # Convert these to regular variables.
150 for key in data:
151 if len(key) > 1 and key[len(key) - 1] == '%':
152 data[key[:-1]] = data[key]
153 del data[key]
155 print gn_helpers.ToGNString(data)
157 if __name__ == '__main__':
158 try:
159 main()
160 except Exception, e:
161 print str(e)
162 sys.exit(1)