Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(275)

Side by Side Diff: gn/gypi_to_gn.py

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

Powered by Google App Engine
This is Rietveld 408576698