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

Side by Side Diff: tools/isolate/merge_gyp.py

Issue 10027006: Rename tree_creator.py to run_test_from_archive.py (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 8 years, 8 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 | Annotate | Revision Log
« no previous file with comments | « tools/isolate/isolate_test.py ('k') | tools/isolate/merge_gyp_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Merges multiple OS-specific gyp dependency lists into one that works on all
7 of them.
8
9
10 The logic is relatively simple. Takes the current conditions, add more
11 condition, find the strict subset. Done.
12 """
13
14 import copy
15 import logging
16 import optparse
17 import re
18 import sys
19
20 import trace_inputs
21
22
23 def union(lhs, rhs):
24 """Merges two compatible datastructures composed of dict/list/set."""
25 assert lhs is not None or rhs is not None
26 if lhs is None:
27 return copy.deepcopy(rhs)
28 if rhs is None:
29 return copy.deepcopy(lhs)
30 assert type(lhs) == type(rhs), (lhs, rhs)
31 if isinstance(lhs, dict):
32 return dict((k, union(lhs.get(k), rhs.get(k))) for k in set(lhs).union(rhs))
33 elif isinstance(lhs, set):
34 # Do not go inside the set.
35 return lhs.union(rhs)
36 elif isinstance(lhs, list):
37 # Do not go inside the list.
38 return lhs + rhs
39 assert False, type(lhs)
40
41
42 def process_variables(for_os, variables):
43 """Extracts files and dirs from the |variables| dict.
44
45 Returns a list of exactly two items. Each item is a dict that maps a string
46 to a set (of strings).
47
48 In the first item, the keys are file names, and the values are sets of OS
49 names, like "win" or "mac". In the second item, the keys are directory names,
50 and the values are sets of OS names too.
51 """
52 VALID_VARIABLES = ['isolate_files', 'isolate_dirs']
53
54 # Verify strictness.
55 assert isinstance(variables, dict), variables
56 assert set(VALID_VARIABLES).issuperset(set(variables)), variables.keys()
57 for items in variables.itervalues():
58 assert isinstance(items, list), items
59 assert all(isinstance(i, basestring) for i in items), items
60
61 # Returns [files, dirs]
62 return [
63 dict((name, set([for_os])) for name in variables.get(var, []))
64 for var in VALID_VARIABLES
65 ]
66
67
68 def eval_content(content):
69 """Evaluates a GYP file and return the value defined in it."""
70 globs = {'__builtins__': None}
71 locs = {}
72 value = eval(content, globs, locs)
73 assert locs == {}, locs
74 assert globs == {'__builtins__': None}, globs
75 return value
76
77
78 def _process_inner(for_os, inner, old_files, old_dirs, old_os):
79 """Processes the variables inside a condition.
80
81 Only meant to be called by parse_gyp_dict().
82
83 Args:
84 - for_os: OS where the references are tracked for.
85 - inner: Inner dictionary to process.
86 - old_files: Previous list of files to union with.
87 - old_dirs: Previous list of directories to union with.
88 - old_os: Previous list of OSes referenced to union with.
89
90 Returns:
91 - A tuple of (files, dirs, os) where each list is a union of the new
92 dependencies found for this OS, as referenced by for_os, and the previous
93 list.
94 """
95 assert isinstance(inner, dict), inner
96 assert set(['variables']).issuperset(set(inner)), inner.keys()
97 new_files, new_dirs = process_variables(for_os, inner.get('variables', {}))
98 if new_files or new_dirs:
99 old_os = old_os.union([for_os.lstrip('!')])
100 return union(old_files, new_files), union(old_dirs, new_dirs), old_os
101
102
103 def parse_gyp_dict(value):
104 """Parses a gyp dict as returned by eval_content().
105
106 |value| is the loaded dictionary that was defined in the gyp file.
107
108 Returns a 3-tuple, where the first two items are the same as the items
109 returned by process_variable() in the same order, and the last item is a set
110 of strings of all OSs seen in the input dict.
111
112 The expected format is strict, anything diverting from the format below will
113 fail:
114 {
115 'variables': {
116 'isolate_files': [
117 ...
118 ],
119 'isolate_dirs: [
120 ...
121 ],
122 },
123 'conditions': [
124 ['OS=="<os>"', {
125 'variables': {
126 ...
127 },
128 }, { # else
129 'variables': {
130 ...
131 },
132 }],
133 ...
134 ],
135 }
136 """
137 assert isinstance(value, dict), value
138 VALID_ROOTS = ['variables', 'conditions']
139 assert set(VALID_ROOTS).issuperset(set(value)), value.keys()
140
141 # Global level variables.
142 oses = set()
143 files, dirs = process_variables(None, value.get('variables', {}))
144
145 # OS specific variables.
146 conditions = value.get('conditions', [])
147 assert isinstance(conditions, list), conditions
148 for condition in conditions:
149 assert isinstance(condition, list), condition
150 assert 2 <= len(condition) <= 3, condition
151 m = re.match(r'OS==\"([a-z]+)\"', condition[0])
152 assert m, condition[0]
153 condition_os = m.group(1)
154
155 files, dirs, oses = _process_inner(
156 condition_os, condition[1], files, dirs, oses)
157
158 if len(condition) == 3:
159 files, dirs, oses = _process_inner(
160 '!' + condition_os, condition[2], files, dirs, oses)
161
162 # TODO(maruel): _expand_negative() should be called here, because otherwise
163 # the OSes the negative condition represents is lost once the gyps are merged.
164 # This cause an invalid expansion in reduce_inputs() call.
165 return files, dirs, oses
166
167
168 def parse_gyp_dicts(gyps):
169 """Parses each gyp file and returns the merged results.
170
171 It only loads what parse_gyp_dict() can process.
172
173 Return values:
174 files: dict(filename, set(OS where this filename is a dependency))
175 dirs: dict(dirame, set(OS where this dirname is a dependency))
176 oses: set(all the OSes referenced)
177 """
178 files = {}
179 dirs = {}
180 oses = set()
181 for gyp in gyps:
182 with open(gyp, 'rb') as gyp_file:
183 content = gyp_file.read()
184 gyp_files, gyp_dirs, gyp_oses = parse_gyp_dict(eval_content(content))
185 files = union(gyp_files, files)
186 dirs = union(gyp_dirs, dirs)
187 oses |= gyp_oses
188 return files, dirs, oses
189
190
191 def _expand_negative(items, oses):
192 """Converts all '!foo' value in the set by oses.difference('foo')."""
193 assert None not in oses and len(oses) >= 2, oses
194 for name in items:
195 if None in items[name]:
196 # Shortcut any item having None in their set. An item listed in None means
197 # the item is a dependency on all OSes. As such, there is no need to list
198 # any OS.
199 items[name] = set([None])
200 continue
201 for neg in [o for o in items[name] if o.startswith('!')]:
202 # Replace it with the inverse.
203 items[name] = items[name].union(oses.difference([neg[1:]]))
204 items[name].remove(neg)
205 if items[name] == oses:
206 items[name] = set([None])
207
208
209 def _compact_negative(items, oses):
210 """Converts all oses.difference('foo') to '!foo'.
211
212 It is doing the reverse of _expand_negative().
213 """
214 assert None not in oses and len(oses) >= 3, oses
215 for name in items:
216 missing = oses.difference(items[name])
217 if len(missing) == 1:
218 # Replace it with a negative.
219 items[name] = set(['!' + tuple(missing)[0]])
220
221
222 def reduce_inputs(files, dirs, oses):
223 """Reduces the variables to their strictest minimum."""
224 # Construct the inverse map first.
225 # Look at each individual file and directory, map where they are used and
226 # reconstruct the inverse dictionary.
227 # First, expands all '!' builders into the reverse.
228 # TODO(maruel): This is too late to call _expand_negative(). The exact list
229 # negative OSes condition it represents is lost at that point.
230 _expand_negative(files, oses)
231 _expand_negative(dirs, oses)
232
233 # Do not convert back to negative if only 2 OSes were merged. It is easier to
234 # read this way.
235 if len(oses) > 2:
236 _compact_negative(files, oses)
237 _compact_negative(dirs, oses)
238
239 return files, dirs
240
241
242 def convert_to_gyp(files, dirs):
243 """Regenerates back a gyp-like configuration dict from files and dirs
244 mappings.
245
246 Sort the lists.
247 """
248 # First, inverse the mapping to make it dict first.
249 config = {}
250 def to_cond(items, name):
251 for item, oses in items.iteritems():
252 for cond_os in oses:
253 condition_values = config.setdefault(
254 None if cond_os is None else cond_os.lstrip('!'),
255 [{}, {}])
256 # If condition is negative, use index 1, else use index 0.
257 condition_value = condition_values[int((cond_os or '').startswith('!'))]
258 # The list of items (files or dirs). Append the new item and keep the
259 # list sorted.
260 l = condition_value.setdefault('variables', {}).setdefault(name, [])
261 l.append(item)
262 l.sort()
263
264 to_cond(files, 'isolate_files')
265 to_cond(dirs, 'isolate_dirs')
266
267 out = {}
268 for o in sorted(config):
269 d = config[o]
270 if o is None:
271 assert not d[1]
272 out = union(out, d[0])
273 else:
274 c = out.setdefault('conditions', [])
275 if d[1]:
276 c.append(['OS=="%s"' % o] + d)
277 else:
278 c.append(['OS=="%s"' % o] + d[0:1])
279 return out
280
281
282 def main():
283 parser = optparse.OptionParser(
284 usage='%prog <options> [file1] [file2] ...')
285 parser.add_option(
286 '-v', '--verbose', action='count', default=0, help='Use multiple times')
287
288 options, args = parser.parse_args()
289 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)]
290 logging.basicConfig(
291 level=level,
292 format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s')
293
294 trace_inputs.pretty_print(
295 convert_to_gyp(*reduce_inputs(*parse_gyp_dicts(args))),
296 sys.stdout)
297 return 0
298
299
300 if __name__ == '__main__':
301 sys.exit(main())
OLDNEW
« no previous file with comments | « tools/isolate/isolate_test.py ('k') | tools/isolate/merge_gyp_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698