OLD | NEW |
---|---|
1 # Copyright (c) 2014 Google Inc. All rights reserved. | 1 # Copyright (c) 2014 Google Inc. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """ | 5 """ |
6 This script is intended for use as a GYP_GENERATOR. It takes as input (by way of | 6 This script is intended for use as a GYP_GENERATOR. It takes as input (by way of |
7 the generator flag config_path) the path of a json file that dictates the files | 7 the generator flag config_path) the path of a json file that dictates the files |
8 and targets to search for. The following keys are supported: | 8 and targets to search for. The following keys are supported: |
9 files: list of paths (relative) of the files to search for. | 9 files: list of paths (relative) of the files to search for. |
10 targets: list of targets to search for. The target names are unqualified. | 10 targets: list of targets to search for. The target names are unqualified. |
11 ignore_targets: list of targets not to include in effected_targets. If a target | |
scottmg
2014/08/15 19:44:45
All the 'effected/Effected's should be 'affected/A
sky
2014/08/15 19:58:12
I *knew* I would get that wrong;)
| |
12 that any of these targets depend on is modified the target won't be output. | |
13 These targets will be output if they contained one of the supplied file names. | |
scottmg
2014/08/15 21:30:24
so, if there's a target in ignored_targets, and so
sky
2014/08/15 22:39:37
Ya, sorry, this is all rather complex. Consider th
| |
11 | 14 |
12 The following is output: | 15 The following is output: |
13 error: only supplied if there is an error. | 16 error: only supplied if there is an error. |
14 warning: only supplied if there is a warning. | 17 warning: only supplied if there is a warning. |
15 targets: the set of targets passed in via targets that either directly or | 18 targets: the set of targets passed in via targets that either directly or |
16 indirectly depend upon the set of paths supplied in files. | 19 indirectly depend upon the set of paths supplied in files. This is not output |
17 status: indicates if any of the supplied files matched at least one target. | 20 effected_targets: minimal set of targets that directly depend on the changed |
21 files. The output from this could be passed into a compile step to build the | |
22 minimal set of targets that are effected by the set of changed files. | |
23 status: outputs one of three values: none of the supplied files were found, | |
24 one of the include files changed so that it should be assumed everything | |
25 changed (in this case targets and effected_targets are not output) or at | |
26 least one file was found. | |
18 | 27 |
19 If the generator flag analyzer_output_path is specified, output is written | 28 If the generator flag analyzer_output_path is specified, output is written |
20 there. Otherwise output is written to stdout. | 29 there. Otherwise output is written to stdout. |
21 """ | 30 """ |
22 | 31 |
23 import gyp.common | 32 import gyp.common |
24 import gyp.ninja_syntax as ninja_syntax | 33 import gyp.ninja_syntax as ninja_syntax |
25 import json | 34 import json |
26 import os | 35 import os |
27 import posixpath | 36 import posixpath |
28 import sys | 37 import sys |
29 | 38 |
30 debug = False | 39 debug = False |
31 | 40 |
32 found_dependency_string = 'Found dependency' | 41 found_dependency_string = 'Found dependency' |
33 no_dependency_string = 'No dependencies' | 42 no_dependency_string = 'No dependencies' |
43 # Status when it should be assumed that everything has changed. | |
44 all_changed_string = 'Found dependency (all)' | |
34 | 45 |
35 # MatchStatus is used indicate if and how a target depends upon the supplied | 46 # MatchStatus is used indicate if and how a target depends upon the supplied |
36 # sources. | 47 # sources. |
37 # The target's sources contain one of the supplied paths. | |
38 MATCH_STATUS_MATCHES = 1 | 48 MATCH_STATUS_MATCHES = 1 |
39 # The target has a dependency on another target that contains one of the | 49 # The target has a dependency on another target that contains one of the |
40 # supplied paths. | 50 # supplied paths. |
41 MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 | 51 MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 |
42 # The target's sources weren't in the supplied paths and none of the target's | 52 # The target's sources weren't in the supplied paths and none of the target's |
43 # dependencies depend upon a target that matched. | 53 # dependencies depend upon a target that matched. |
44 MATCH_STATUS_DOESNT_MATCH = 3 | 54 MATCH_STATUS_DOESNT_MATCH = 3 |
45 # The target doesn't contain the source, but the dependent targets have not yet | 55 # The target doesn't contain the source, but the dependent targets have not yet |
46 # been visited to determine a more specific status yet. | 56 # been visited to determine a more specific status yet. |
47 MATCH_STATUS_TBD = 4 | 57 MATCH_STATUS_TBD = 4 |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
151 results) | 161 results) |
152 if 'rules' in target_dict: | 162 if 'rules' in target_dict: |
153 for rule in target_dict['rules']: | 163 for rule in target_dict['rules']: |
154 _ExtractSourcesFromAction(rule, base_path, base_path_components, results) | 164 _ExtractSourcesFromAction(rule, base_path, base_path_components, results) |
155 | 165 |
156 return results | 166 return results |
157 | 167 |
158 | 168 |
159 class Target(object): | 169 class Target(object): |
160 """Holds information about a particular target: | 170 """Holds information about a particular target: |
161 deps: set of the names of direct dependent targets. | 171 deps: set of Targets this Target depends upon. This is not recursive, only the |
162 match_staus: one of the MatchStatus values""" | 172 direct dependent Targets. |
163 def __init__(self): | 173 match_staus: one of the MatchStatus values. |
174 back_deps: set of Targets that have a dependency on this Target. | |
175 visited: used during iteration to indicate whether we've visited this target. | |
176 This is used for two iterations, once in building the set of Targets and | |
177 again in _GetEffectedTargets(). | |
178 name: fully qualified name of the target.""" | |
179 def __init__(self, name): | |
164 self.deps = set() | 180 self.deps = set() |
165 self.match_status = MATCH_STATUS_TBD | 181 self.match_status = MATCH_STATUS_TBD |
182 self.back_deps = set() | |
183 self.visited = False | |
184 self.name = name | |
166 | 185 |
167 | 186 |
168 class Config(object): | 187 class Config(object): |
169 """Details what we're looking for | 188 """Details what we're looking for |
170 files: set of files to search for | 189 files: set of files to search for |
171 targets: see file description for details""" | 190 targets: see file description for details. |
191 ignore_targets: see file description for details""" | |
172 def __init__(self): | 192 def __init__(self): |
173 self.files = [] | 193 self.files = [] |
174 self.targets = [] | 194 self.targets = set() |
195 self.ignore_targets = set() | |
175 | 196 |
176 def Init(self, params): | 197 def Init(self, params): |
177 """Initializes Config. This is a separate method as it raises an exception | 198 """Initializes Config. This is a separate method as it raises an exception |
178 if there is a parse error.""" | 199 if there is a parse error.""" |
179 generator_flags = params.get('generator_flags', {}) | 200 generator_flags = params.get('generator_flags', {}) |
180 config_path = generator_flags.get('config_path', None) | 201 config_path = generator_flags.get('config_path', None) |
181 if not config_path: | 202 if not config_path: |
182 return | 203 return |
183 try: | 204 try: |
184 f = open(config_path, 'r') | 205 f = open(config_path, 'r') |
185 config = json.load(f) | 206 config = json.load(f) |
186 f.close() | 207 f.close() |
187 except IOError: | 208 except IOError: |
188 raise Exception('Unable to open file ' + config_path) | 209 raise Exception('Unable to open file ' + config_path) |
189 except ValueError as e: | 210 except ValueError as e: |
190 raise Exception('Unable to parse config file ' + config_path + str(e)) | 211 raise Exception('Unable to parse config file ' + config_path + str(e)) |
191 if not isinstance(config, dict): | 212 if not isinstance(config, dict): |
192 raise Exception('config_path must be a JSON file containing a dictionary') | 213 raise Exception('config_path must be a JSON file containing a dictionary') |
193 self.files = config.get('files', []) | 214 self.files = config.get('files', []) |
194 # Coalesce duplicates | 215 # Convert to sets so that we remove duplicates. |
195 self.targets = list(set(config.get('targets', []))) | 216 self.targets = set(config.get('targets', [])) |
217 self.ignore_targets = set(config.get('ignore_targets', [])) | |
196 | 218 |
197 | 219 |
198 def _WasBuildFileModified(build_file, data, files): | 220 def _WasBuildFileModified(build_file, data, files): |
199 """Returns true if the build file |build_file| is either in |files| or | 221 """Returns true if the build file |build_file| is either in |files| or |
200 one of the files included by |build_file| is in |files|.""" | 222 one of the files included by |build_file| is in |files|.""" |
201 if _ToGypPath(build_file) in files: | 223 if _ToGypPath(build_file) in files: |
202 if debug: | 224 if debug: |
203 print 'gyp file modified', build_file | 225 print 'gyp file modified', build_file |
204 return True | 226 return True |
205 | 227 |
206 # First element of included_files is the file itself. | 228 # First element of included_files is the file itself. |
207 if len(data[build_file]['included_files']) <= 1: | 229 if len(data[build_file]['included_files']) <= 1: |
208 return False | 230 return False |
209 | 231 |
210 for include_file in data[build_file]['included_files'][1:]: | 232 for include_file in data[build_file]['included_files'][1:]: |
211 # |included_files| are relative to the directory of the |build_file|. | 233 # |included_files| are relative to the directory of the |build_file|. |
212 rel_include_file = \ | 234 rel_include_file = \ |
213 _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) | 235 _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) |
214 if rel_include_file in files: | 236 if rel_include_file in files: |
215 if debug: | 237 if debug: |
216 print 'included gyp file modified, gyp_file=', build_file, \ | 238 print 'included gyp file modified, gyp_file=', build_file, \ |
217 'included file=', rel_include_file | 239 'included file=', rel_include_file |
218 return True | 240 return True |
219 return False | 241 return False |
220 | 242 |
221 | 243 |
222 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files): | 244 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, |
223 """Generates a dictionary with the key the name of a target and the value a | 245 build_files): |
224 Target. |toplevel_dir| is the root of the source tree. If the sources of | 246 """Returns a tuple of the following: |
225 a target match that of |files|, then |target.matched| is set to True. | 247 . A dictionary mapping from fully qualified name to Target. |
226 This returns a tuple of the dictionary and whether at least one target's | 248 . A list of the targets that have a source file in |files|. |
227 sources listed one of the paths in |files|.""" | 249 . Set of root Targets reachable from the the files |build_files|. |
250 This sets the |match_status| of the targets that contain any of the source | |
251 files in |files| to MATCH_STATUS_MATCHES. | |
252 |toplevel_dir| is the root of the source tree.""" | |
253 # Maps from target name to Target. | |
228 targets = {} | 254 targets = {} |
229 | 255 |
256 # Targets that matched. | |
257 matching_targets = [] | |
258 | |
230 # Queue of targets to visit. | 259 # Queue of targets to visit. |
231 targets_to_visit = target_list[:] | 260 targets_to_visit = target_list[:] |
232 | 261 |
233 matched = False | |
234 | |
235 # Maps from build file to a boolean indicating whether the build file is in | 262 # Maps from build file to a boolean indicating whether the build file is in |
236 # |files|. | 263 # |files|. |
237 build_file_in_files = {} | 264 build_file_in_files = {} |
238 | 265 |
266 # Root targets across all files. | |
267 roots = set() | |
268 | |
269 # Set of Targets in |build_files|. | |
270 build_file_targets = set() | |
271 | |
239 while len(targets_to_visit) > 0: | 272 while len(targets_to_visit) > 0: |
240 target_name = targets_to_visit.pop() | 273 target_name = targets_to_visit.pop() |
241 if target_name in targets: | 274 if not target_name in targets: |
275 target = Target(target_name) | |
276 targets[target_name] = target | |
277 roots.add(target) | |
278 elif targets[target_name].visited: | |
242 continue | 279 continue |
280 else: | |
281 target = targets[target_name] | |
243 | 282 |
244 target = Target() | 283 target.visited = True |
245 targets[target_name] = target | |
246 | 284 |
247 build_file = gyp.common.ParseQualifiedTarget(target_name)[0] | 285 build_file = gyp.common.ParseQualifiedTarget(target_name)[0] |
248 if not build_file in build_file_in_files: | 286 if not build_file in build_file_in_files: |
249 build_file_in_files[build_file] = \ | 287 build_file_in_files[build_file] = \ |
250 _WasBuildFileModified(build_file, data, files) | 288 _WasBuildFileModified(build_file, data, files) |
251 | 289 |
290 if build_file in build_files: | |
291 build_file_targets.add(target) | |
292 | |
252 # If a build file (or any of its included files) is modified we assume all | 293 # If a build file (or any of its included files) is modified we assume all |
253 # targets in the file are modified. | 294 # targets in the file are modified. |
254 if build_file_in_files[build_file]: | 295 if build_file_in_files[build_file]: |
296 print 'matching target from modified build file', target_name | |
255 target.match_status = MATCH_STATUS_MATCHES | 297 target.match_status = MATCH_STATUS_MATCHES |
256 matched = True | 298 matching_targets.append(target) |
257 else: | 299 else: |
258 sources = _ExtractSources(target_name, target_dicts[target_name], | 300 sources = _ExtractSources(target_name, target_dicts[target_name], |
259 toplevel_dir) | 301 toplevel_dir) |
260 for source in sources: | 302 for source in sources: |
261 if source in files: | 303 if source in files: |
304 print 'target', target_name, 'matches', source | |
262 target.match_status = MATCH_STATUS_MATCHES | 305 target.match_status = MATCH_STATUS_MATCHES |
263 matched = True | 306 matching_targets.append(target) |
264 break | 307 break |
265 | 308 |
309 # Add dependencies to visit as well as updating back pointers for deps. | |
266 for dep in target_dicts[target_name].get('dependencies', []): | 310 for dep in target_dicts[target_name].get('dependencies', []): |
267 targets[target_name].deps.add(dep) | |
268 targets_to_visit.append(dep) | 311 targets_to_visit.append(dep) |
269 | 312 |
270 return targets, matched | 313 if not dep in targets: |
314 dep_target = Target(dep) | |
315 targets[dep] = dep_target | |
316 else: | |
317 dep_target = targets[dep] | |
318 roots.discard(dep_target) | |
319 | |
320 target.deps.add(dep_target) | |
321 dep_target.back_deps.add(target) | |
322 | |
323 return targets, matching_targets, roots & build_file_targets | |
271 | 324 |
272 | 325 |
273 def _GetUnqualifiedToQualifiedMapping(all_targets, to_find): | 326 def _GetUnqualifiedToTargetMapping(all_targets, to_find): |
274 """Returns a mapping (dictionary) from unqualified name to qualified name for | 327 """Returns a mapping (dictionary) from unqualified name to Target for all the |
275 all the targets in |to_find|.""" | 328 Targets in |to_find|.""" |
276 result = {} | 329 result = {} |
277 if not to_find: | 330 if not to_find: |
278 return result | 331 return result |
279 to_find = set(to_find) | 332 to_find = set(to_find) |
280 for target_name in all_targets.keys(): | 333 for target_name in all_targets.keys(): |
281 extracted = gyp.common.ParseQualifiedTarget(target_name) | 334 extracted = gyp.common.ParseQualifiedTarget(target_name) |
282 if len(extracted) > 1 and extracted[1] in to_find: | 335 if len(extracted) > 1 and extracted[1] in to_find: |
283 to_find.remove(extracted[1]) | 336 to_find.remove(extracted[1]) |
284 result[extracted[1]] = target_name | 337 result[extracted[1]] = all_targets[target_name] |
285 if not to_find: | 338 if not to_find: |
286 return result | 339 return result |
287 return result | 340 return result |
288 | 341 |
289 | 342 |
290 def _DoesTargetDependOn(target, all_targets): | 343 def _DoesTargetDependOn(target): |
291 """Returns true if |target| or any of its dependencies matches the supplied | 344 """Returns true if |target| or any of its dependencies matches the supplied |
292 set of paths. This updates |matches| of the Targets as it recurses. | 345 set of paths. This updates |matches| of the Targets as it recurses. |
293 target: the Target to look for. | 346 target: the Target to look for.""" |
294 all_targets: mapping from target name to Target. | |
295 matching_targets: set of targets looking for.""" | |
296 if target.match_status == MATCH_STATUS_DOESNT_MATCH: | 347 if target.match_status == MATCH_STATUS_DOESNT_MATCH: |
297 return False | 348 return False |
298 if target.match_status == MATCH_STATUS_MATCHES or \ | 349 if target.match_status == MATCH_STATUS_MATCHES or \ |
299 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: | 350 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: |
300 return True | 351 return True |
301 for dep_name in target.deps: | 352 for dep in target.deps: |
302 dep_target = all_targets[dep_name] | 353 if _DoesTargetDependOn(dep): |
303 if _DoesTargetDependOn(dep_target, all_targets): | 354 target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY |
304 dep_target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY | |
305 return True | 355 return True |
306 dep_target.match_status = MATCH_STATUS_DOESNT_MATCH | 356 target.match_status = MATCH_STATUS_DOESNT_MATCH |
307 return False | 357 return False |
308 | 358 |
309 | 359 |
310 def _GetTargetsDependingOn(all_targets, possible_targets): | 360 def _GetTargetsDependingOn(possible_targets): |
311 """Returns the list of targets in |possible_targets| that depend (either | 361 """Returns the list of Targets in |possible_targets| that depend (either |
312 directly on indirectly) on the matched files. | 362 directly on indirectly) on the matched targets. |
313 all_targets: mapping from target name to Target. | |
314 possible_targets: targets to search from.""" | 363 possible_targets: targets to search from.""" |
315 found = [] | 364 found = [] |
316 for target in possible_targets: | 365 for target in possible_targets: |
317 if _DoesTargetDependOn(all_targets[target], all_targets): | 366 if _DoesTargetDependOn(target): |
318 # possible_targets was initially unqualified, keep it unqualified. | 367 found.append(target) |
319 found.append(gyp.common.ParseQualifiedTarget(target)[1]) | |
320 return found | 368 return found |
321 | 369 |
322 | 370 |
371 def _RemoveTargetsThatDependOn(target, targets): | |
372 """Removes |target| from |targets|. If |targets| is not empty recurses through | |
373 descendants.""" | |
374 if target in targets: | |
375 targets.remove(target) | |
376 return len(targets) == 0 | |
377 | |
378 if target.visited or target.match_status == MATCH_STATUS_DOESNT_MATCH: | |
379 return False | |
380 | |
381 for child_target in target.deps: | |
382 if _RemoveTargetsThatDependOn(child_target, targets): | |
383 return True | |
384 | |
385 return False | |
386 | |
387 | |
388 def _AddFirstModified(target, result): | |
389 """Adds |target| to |result| if it matches, otherwise recursively descends | |
390 through children doing the same. | |
391 |result|: targets that match are added here. Once a target matches none of its | |
392 dependencies are considered.""" | |
393 if target.visited: | |
394 return | |
395 | |
396 if target.match_status == MATCH_STATUS_MATCHES or \ | |
397 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: | |
398 # Because of iteration order it is possible for |target| to depend on other | |
399 # targets in |result|. Prune them. | |
400 if result: | |
401 _RemoveTargetsThatDependOn(target, result) | |
402 result.add(target) | |
403 target.visited = True | |
404 return | |
405 | |
406 target.visited = True | |
407 | |
408 if target.match_status == MATCH_STATUS_DOESNT_MATCH: | |
409 return | |
410 | |
411 for child_target in target.deps: | |
412 _AddFirstModified(child_target, result) | |
413 | |
414 | |
415 def _MarkVisitedAndMatches(target): | |
416 """Marks |target| as visited and matches (assuming it isn't already), and | |
417 recurses through dependencies. Does nothing if already |target| has already | |
418 been visited.""" | |
419 if target.visited: | |
420 return | |
421 | |
422 target.visited = True | |
423 | |
424 if target.match_status != MATCH_STATUS_MATCHES: | |
425 target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY | |
426 | |
427 for child_target in target.deps: | |
428 _MarkVisitedAndMatches(child_target) | |
429 | |
430 | |
431 def _MarkDepsVisitedAndMatches(target, ignore): | |
432 """Marks all the |deps| of |target| that are not in |ignore| as visited and | |
433 matches. See _MarkVisitedAndMatches() for details.""" | |
434 for child_target in target.deps: | |
435 if child_target not in ignore: | |
436 _MarkVisitedAndMatches(child_target) | |
437 | |
438 | |
439 def _GetEffectedTargets(matching_targets, matched_search_targets, roots, | |
440 ignore_targets): | |
441 """Returns the set of Targets that directly depend on |matching_targets| and | |
442 have not yet matched one of the supplied targets. | |
443 matched_search_targets: set of targets passed in (by way of config) that | |
444 matched a dependency. | |
445 roots: set of root targets in the build files to search from. | |
446 ignore_targets: see file description for details.""" | |
447 # This function strives to minimize the set of targets it returns. For example | |
448 # if A and B are in matching_targets, but B depends upon A then we only need | |
449 # return A for both to be compiled. | |
450 # | |
451 # When we get here all the Targets have their |visited| state set to False. | |
452 # As not all Targets may have been visited the |match_status| may still be | |
453 # TBD for many. For example, if B depends on A and A matched, then B's state | |
454 # may still be TBD. We need to ensure the match_status is set correctly so | |
455 # then when we resurse from the root targets we don't attempt to include | |
456 # a Target that would be included by way of another Targets depedendencies. | |
457 | |
458 # First step, visit the search targets, recursively marking all of their | |
459 # descendants as visited and matching. | |
460 for target in matched_search_targets: | |
461 _MarkDepsVisitedAndMatches(target, set()) | |
462 | |
463 # For all the targets that contained matching files we need to mark any of | |
464 # their dependencies as visited and matching. Similarly any targets that | |
465 # depend on |matching_targets| needs to be marked as matching as well. We have | |
466 # to take care not to mark the |matching_targets| (or the |ignore_targets|) | |
467 # visited, otherwise we won't add it to the result later on. | |
468 for target in matching_targets: | |
469 _MarkDepsVisitedAndMatches(target, set()) | |
470 ignore_plus_target = set(ignore_targets) | |
471 ignore_plus_target.add(target) | |
472 for back_dep_target in target.back_deps: | |
473 if back_dep_target not in ignore_targets: | |
474 back_dep_target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY | |
475 _MarkDepsVisitedAndMatches(back_dep_target, ignore_plus_target) | |
476 | |
477 # Reset the status of any in |ignore_targets| that match by way of a | |
478 # dependency. This way we won't incorrectly pull them in again (they may have | |
479 # matched by dependency). We don't reset those that matched explicitly as they | |
480 # have to be compiled. | |
481 for target in ignore_targets: | |
482 if target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: | |
483 target.match_status = MATCH_STATUS_TBD | |
484 | |
485 # Finally we can search down from the root Targets adding the first Targets | |
486 # that match. | |
487 result = set() | |
488 for root in roots: | |
489 _AddFirstModified(root, result) | |
490 | |
491 return result | |
492 | |
493 | |
323 def _WriteOutput(params, **values): | 494 def _WriteOutput(params, **values): |
324 """Writes the output, either to stdout or a file is specified.""" | 495 """Writes the output, either to stdout or a file is specified.""" |
325 output_path = params.get('generator_flags', {}).get( | 496 output_path = params.get('generator_flags', {}).get( |
326 'analyzer_output_path', None) | 497 'analyzer_output_path', None) |
327 if not output_path: | 498 if not output_path: |
328 print json.dumps(values) | 499 print json.dumps(values) |
329 return | 500 return |
330 try: | 501 try: |
331 f = open(output_path, 'w') | 502 f = open(output_path, 'w') |
332 f.write(json.dumps(values) + '\n') | 503 f.write(json.dumps(values) + '\n') |
333 f.close() | 504 f.close() |
334 except IOError as e: | 505 except IOError as e: |
335 print 'Error writing to output file', output_path, str(e) | 506 print 'Error writing to output file', output_path, str(e) |
336 | 507 |
337 | 508 |
509 def _WasIncludeFileModified(params, files): | |
510 """Returns true if one of the files in |files| is in the set of included | |
511 files.""" | |
512 if params['options'].includes: | |
513 for include in params['options'].includes: | |
514 if _ToGypPath(include) in files: | |
515 print 'Include file modified, assuming all changed', include | |
516 return True | |
517 return False | |
518 | |
519 | |
520 def _NamesNotIn(names, mapping): | |
521 """Returns a list of the values in |names| that are not in |mapping|.""" | |
522 return [name for name in names if name not in mapping] | |
523 | |
524 | |
525 def _LookupTargets(names, mapping): | |
526 """Returns a list of the mapping[name] for each value in |names| that is in | |
527 |mapping|.""" | |
528 return [mapping[name] for name in names if name in mapping] | |
529 | |
530 | |
338 def CalculateVariables(default_variables, params): | 531 def CalculateVariables(default_variables, params): |
339 """Calculate additional variables for use in the build (called by gyp).""" | 532 """Calculate additional variables for use in the build (called by gyp).""" |
340 flavor = gyp.common.GetFlavor(params) | 533 flavor = gyp.common.GetFlavor(params) |
341 if flavor == 'mac': | 534 if flavor == 'mac': |
342 default_variables.setdefault('OS', 'mac') | 535 default_variables.setdefault('OS', 'mac') |
343 elif flavor == 'win': | 536 elif flavor == 'win': |
344 default_variables.setdefault('OS', 'win') | 537 default_variables.setdefault('OS', 'win') |
345 # Copy additional generator configuration data from VS, which is shared | 538 # Copy additional generator configuration data from VS, which is shared |
346 # by the Windows Ninja generator. | 539 # by the Windows Ninja generator. |
347 import gyp.generator.msvs as msvs_generator | 540 import gyp.generator.msvs as msvs_generator |
(...skipping 16 matching lines...) Expand all Loading... | |
364 try: | 557 try: |
365 config.Init(params) | 558 config.Init(params) |
366 if not config.files: | 559 if not config.files: |
367 raise Exception('Must specify files to analyze via config_path generator ' | 560 raise Exception('Must specify files to analyze via config_path generator ' |
368 'flag') | 561 'flag') |
369 | 562 |
370 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) | 563 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) |
371 if debug: | 564 if debug: |
372 print 'toplevel_dir', toplevel_dir | 565 print 'toplevel_dir', toplevel_dir |
373 | 566 |
374 matched = False | 567 if _WasIncludeFileModified(params, config.files): |
375 matched_include = False | 568 result_dict = { 'status': all_changed_string } |
569 _WriteOutput(params, **result_dict) | |
570 return | |
376 | 571 |
377 # If one of the modified files is an include file then everything is | 572 all_targets, matching_targets, roots = _GenerateTargets( |
378 # affected. | 573 data, target_list, target_dicts, toplevel_dir, frozenset(config.files), |
379 if params['options'].includes: | 574 params['build_files']) |
380 for include in params['options'].includes: | |
381 if _ToGypPath(include) in config.files: | |
382 if debug: | |
383 print 'include path modified', include | |
384 matched_include = True | |
385 matched = True | |
386 break | |
387 | |
388 if not matched: | |
389 all_targets, matched = _GenerateTargets(data, target_list, target_dicts, | |
390 toplevel_dir, | |
391 frozenset(config.files)) | |
392 | 575 |
393 warning = None | 576 warning = None |
394 if matched_include: | 577 combined_targets = config.targets | config.ignore_targets |
395 output_targets = config.targets | 578 unqualified_mapping = _GetUnqualifiedToTargetMapping(all_targets, |
396 elif matched: | 579 combined_targets) |
397 unqualified_mapping = _GetUnqualifiedToQualifiedMapping( | 580 if len(unqualified_mapping) != len(combined_targets): |
398 all_targets, config.targets) | 581 not_found = _NamesNotIn(combined_targets, unqualified_mapping) |
399 if len(unqualified_mapping) != len(config.targets): | 582 warning = 'Unable to find all targets: ' + str(not_found) |
400 not_found = [] | 583 |
401 for target in config.targets: | 584 if matching_targets: |
402 if not target in unqualified_mapping: | 585 search_targets = _LookupTargets(config.targets, unqualified_mapping) |
403 not_found.append(target) | 586 ignore_targets = _LookupTargets(config.ignore_targets, |
404 warning = 'Unable to find all targets: ' + str(not_found) | 587 unqualified_mapping) |
405 qualified_targets = [] | 588 |
406 for target in config.targets: | 589 matched_search_targets = _GetTargetsDependingOn(search_targets) |
407 if target in unqualified_mapping: | 590 # Reset the visited status for _GetEffectedTargets. |
408 qualified_targets.append(unqualified_mapping[target]) | 591 for target in all_targets.itervalues(): |
409 output_targets = _GetTargetsDependingOn(all_targets, qualified_targets) | 592 target.visited = False |
593 effected_targets = _GetEffectedTargets(matching_targets, | |
594 matched_search_targets, roots, | |
595 ignore_targets) | |
596 matched_search_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] | |
597 for target in matched_search_targets] | |
598 effected_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] | |
599 for target in effected_targets] | |
410 else: | 600 else: |
411 output_targets = [] | 601 matched_search_targets = [] |
602 effected_targets = [] | |
412 | 603 |
413 result_dict = { 'targets': output_targets, | 604 result_dict = { 'targets': matched_search_targets, |
414 'status': found_dependency_string if matched else | 605 'status': found_dependency_string if matching_targets else |
415 no_dependency_string } | 606 no_dependency_string, |
607 'effected_targets': effected_targets} | |
416 if warning: | 608 if warning: |
417 result_dict['warning'] = warning | 609 result_dict['warning'] = warning |
418 _WriteOutput(params, **result_dict) | 610 _WriteOutput(params, **result_dict) |
419 | 611 |
420 except Exception as e: | 612 except Exception as e: |
421 _WriteOutput(params, error=str(e)) | 613 _WriteOutput(params, error=str(e)) |
OLD | NEW |