blob: 6de1a6385df9d8c4d4d1b5b59ce652f0d93463d6 [file] [log] [blame]
brettw@chromium.orgc976b182014-04-06 13:35:10 +09001# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Converts a given gypi file to a python scope and writes the result to stdout.
6
7It is assumed that the file contains a toplevel dictionary, and this script
8will return that dictionary as a GN "scope" (see example below). This script
9does not know anything about GYP and it will not expand variables or execute
brettw@chromium.org485529f2014-05-21 06:52:34 +090010conditions.
11
12It will strip conditions blocks.
13
14A variables block at the top level will be flattened so that the variables
15appear in the root dictionary. This way they can be returned to the GN code.
brettw@chromium.orgc976b182014-04-06 13:35:10 +090016
17Say your_file.gypi looked like this:
18 {
19 'sources': [ 'a.cc', 'b.cc' ],
20 'defines': [ 'ENABLE_DOOM_MELON' ],
21 }
22
23You 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" ])
28
29Notes:
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.
33
34 - The "scope" parameter tells GN to interpret the result as a series of GN
35 variable assignments.
36
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.
40
41Read the values into a target like this:
42 component("mycomponent") {
43 sources = gypi_values.sources
44 defines = gypi_values.defines
45 }
46
47Sometimes your .gypi file will include paths relative to a different
48directory than the current .gn file. In this case, you can rebase them to
49be relative to the current directory.
50 sources = rebase_path(gypi_values.sources, ".",
51 "//path/gypi/input/values/are/relative/to")
brettw@chromium.org4c703702014-04-12 02:23:40 +090052
53This script will tolerate a 'variables' in the toplevel dictionary or not. If
54the toplevel dictionary just contains one item called 'variables', it will be
55collapsed 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
57be embedded into a .gyp file.
58
59This script also has the ability to replace certain substrings in the input.
60Generally this is used to emulate GYP variable expansion. If you passed the
61argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
62the input will be replaced with "bar":
63
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" ])
69
brettw@chromium.orgc976b182014-04-06 13:35:10 +090070"""
71
72import gn_helpers
73from optparse import OptionParser
74import sys
75
76def 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)))
85
86 assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
brettw@chromium.org485529f2014-05-21 06:52:34 +090087
brettw@chromium.orgf0760e32014-06-03 04:18:24 +090088 # 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']
92
brettw@chromium.org485529f2014-05-21 06:52:34 +090093 # 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']
98
brettw6c0f6bf2015-11-26 18:21:25 +090099 # Strip targets and includes in the toplevel, since some files define these
100 # and we can't slurp them in.
brettw@chromium.org57869212014-06-20 13:26:55 +0900101 if 'targets' in file_data:
102 del file_data['targets']
brettw6c0f6bf2015-11-26 18:21:25 +0900103 if 'includes' in file_data:
104 del file_data['includes']
brettw@chromium.org57869212014-06-20 13:26:55 +0900105
brettw@chromium.orgc976b182014-04-06 13:35:10 +0900106 return file_data
107
108
brettw@chromium.org4c703702014-04-12 02:23:40 +0900109def ReplaceSubstrings(values, search_for, replace_with):
110 """Recursively replaces substrings in a value.
111
112 Replaces all substrings of the "search_for" with "repace_with" for all
113 strings occurring in "values". This is done by recursively iterating into
114 lists as well as the keys and values of dictionaries."""
115 if isinstance(values, str):
116 return values.replace(search_for, replace_with)
117
118 if isinstance(values, list):
119 return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
120
121 if isinstance(values, dict):
122 # For dictionaries, do the search for both the key and values.
123 result = {}
124 for key, value in values.items():
125 new_key = ReplaceSubstrings(key, search_for, replace_with)
126 new_value = ReplaceSubstrings(value, search_for, replace_with)
127 result[new_key] = new_value
128 return result
129
130 # Assume everything else is unchanged.
131 return values
132
brettw@chromium.orgc976b182014-04-06 13:35:10 +0900133def main():
134 parser = OptionParser()
brettw@chromium.org4c703702014-04-12 02:23:40 +0900135 parser.add_option("-r", "--replace", action="append",
136 help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
brettw@chromium.orgc976b182014-04-06 13:35:10 +0900137 (options, args) = parser.parse_args()
138
139 if len(args) != 1:
140 raise Exception("Need one argument which is the .gypi file to read.")
141
142 data = LoadPythonDictionary(args[0])
brettw@chromium.org4c703702014-04-12 02:23:40 +0900143 if options.replace:
144 # Do replacements for all specified patterns.
145 for replace in options.replace:
146 split = replace.split('=')
147 # Allow "foo=" to replace with nothing.
148 if len(split) == 1:
149 split.append('')
150 assert len(split) == 2, "Replacement must be of the form 'key=value'."
151 data = ReplaceSubstrings(data, split[0], split[1])
152
brettw@chromium.orgf0760e32014-06-03 04:18:24 +0900153 # Sometimes .gypi files use the GYP syntax with percents at the end of the
154 # variable name (to indicate not to overwrite a previously-defined value):
155 # 'foo%': 'bar',
156 # Convert these to regular variables.
157 for key in data:
158 if len(key) > 1 and key[len(key) - 1] == '%':
159 data[key[:-1]] = data[key]
160 del data[key]
161
brettw@chromium.orgc976b182014-04-06 13:35:10 +0900162 print gn_helpers.ToGNString(data)
163
164if __name__ == '__main__':
165 try:
166 main()
167 except Exception, e:
168 print str(e)
169 sys.exit(1)