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 | 11 |
12 The following is output: | 12 The following is output: |
13 error: only supplied if there is an error. | 13 error: only supplied if there is an error. |
14 targets: the set of targets passed in via targets that either directly or | |
15 indirectly depend upon the set of paths supplied in files. | |
16 build_targets: minimal set of targets that directly depend on the changed | 14 build_targets: minimal set of targets that directly depend on the changed |
17 files and need to be built. The expectation is this set of targets is passed | 15 files and need to be built. The expectation is this set of targets is passed |
18 into a build step. | 16 into a build step. The returned values are either values in the supplied |
| 17 targets, or have a dependency on one of the supplied targets. |
19 status: outputs one of three values: none of the supplied files were found, | 18 status: outputs one of three values: none of the supplied files were found, |
20 one of the include files changed so that it should be assumed everything | 19 one of the include files changed so that it should be assumed everything |
21 changed (in this case targets and build_targets are not output) or at | 20 changed (in this case targets and build_targets are not output) or at |
22 least one file was found. | 21 least one file was found. |
23 invalid_targets: list of supplied targets thare were not found. | 22 invalid_targets: list of supplied targets thare were not found. |
24 | 23 |
| 24 Example: |
| 25 Consider a graph like the following: |
| 26 A D |
| 27 / \ |
| 28 B C |
| 29 A depends upon both B and C, A is of type none and B and C are executables. |
| 30 D is an executable, has no dependencies and nothing depends on it. |
| 31 If |targets| = ["A"] and files = ["b.cc", "d.cc"] (B depends upon b.cc and D |
| 32 depends upon d.cc), then the following is output: |
| 33 |build_targets| = ["B"] B must built as it depends upon the changed file b.cc |
| 34 and the supplied target A depends upon it. A is not output as a build_target |
| 35 as it is of type none with no rules and actions. |
| 36 |
| 37 Even though the file d.cc, which D depends upon, has changed D is not output |
| 38 as none of the supplied targets (A) depend upon D. |
| 39 |
25 If the generator flag analyzer_output_path is specified, output is written | 40 If the generator flag analyzer_output_path is specified, output is written |
26 there. Otherwise output is written to stdout. | 41 there. Otherwise output is written to stdout. |
27 """ | 42 """ |
28 | 43 |
29 import gyp.common | 44 import gyp.common |
30 import gyp.ninja_syntax as ninja_syntax | 45 import gyp.ninja_syntax as ninja_syntax |
31 import json | 46 import json |
32 import os | 47 import os |
33 import posixpath | 48 import posixpath |
34 import sys | 49 import sys |
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
252 if _ToLocalPath(toplevel_dir, rel_include_file) in files: | 267 if _ToLocalPath(toplevel_dir, rel_include_file) in files: |
253 if debug: | 268 if debug: |
254 print 'included gyp file modified, gyp_file=', build_file, \ | 269 print 'included gyp file modified, gyp_file=', build_file, \ |
255 'included file=', rel_include_file | 270 'included file=', rel_include_file |
256 return True | 271 return True |
257 return False | 272 return False |
258 | 273 |
259 | 274 |
260 def _GetOrCreateTargetByName(targets, target_name): | 275 def _GetOrCreateTargetByName(targets, target_name): |
261 """Creates or returns the Target at targets[target_name]. If there is no | 276 """Creates or returns the Target at targets[target_name]. If there is no |
262 Target for |target_name| one is created. Returns a tuple of whether a new | 277 Target for |target_name| one is created.""" |
263 Target was created and the Target.""" | |
264 if target_name in targets: | 278 if target_name in targets: |
265 return False, targets[target_name] | 279 return targets[target_name] |
266 target = Target(target_name) | 280 target = Target(target_name) |
267 targets[target_name] = target | 281 targets[target_name] = target |
268 return True, target | 282 return target |
269 | 283 |
270 | 284 |
271 def _DoesTargetTypeRequireBuild(target_dict): | 285 def _DoesTargetTypeRequireBuild(target_dict): |
272 """Returns true if the target type is such that it needs to be built.""" | 286 """Returns true if the target type is such that it needs to be built.""" |
273 # If a 'none' target has rules or actions we assume it requires a build. | 287 # If a 'none' target has rules or actions we assume it requires a build. |
274 return bool(target_dict['type'] != 'none' or | 288 return bool(target_dict['type'] != 'none' or |
275 target_dict.get('actions') or target_dict.get('rules')) | 289 target_dict.get('actions') or target_dict.get('rules')) |
276 | 290 |
277 | 291 |
278 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, | 292 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, |
279 build_files): | 293 build_files): |
280 """Returns a tuple of the following: | 294 """Returns a tuple of the following: |
281 . A dictionary mapping from fully qualified name to Target. | 295 . A dictionary mapping from fully qualified name to Target. |
282 . A list of the targets that have a source file in |files|. | 296 . A list of the targets that have a source file in |files|. |
283 . Set of root Targets reachable from the the files |build_files|. | |
284 This sets the |match_status| of the targets that contain any of the source | 297 This sets the |match_status| of the targets that contain any of the source |
285 files in |files| to MATCH_STATUS_MATCHES. | 298 files in |files| to MATCH_STATUS_MATCHES. |
286 |toplevel_dir| is the root of the source tree.""" | 299 |toplevel_dir| is the root of the source tree.""" |
287 # Maps from target name to Target. | 300 # Maps from target name to Target. |
288 targets = {} | 301 targets = {} |
289 | 302 |
290 # Targets that matched. | 303 # Targets that matched. |
291 matching_targets = [] | 304 matching_targets = [] |
292 | 305 |
293 # Queue of targets to visit. | 306 # Queue of targets to visit. |
294 targets_to_visit = target_list[:] | 307 targets_to_visit = target_list[:] |
295 | 308 |
296 # Maps from build file to a boolean indicating whether the build file is in | 309 # Maps from build file to a boolean indicating whether the build file is in |
297 # |files|. | 310 # |files|. |
298 build_file_in_files = {} | 311 build_file_in_files = {} |
299 | 312 |
300 # Root targets across all files. | |
301 roots = set() | |
302 | |
303 # Set of Targets in |build_files|. | |
304 build_file_targets = set() | |
305 | |
306 while len(targets_to_visit) > 0: | 313 while len(targets_to_visit) > 0: |
307 target_name = targets_to_visit.pop() | 314 target_name = targets_to_visit.pop() |
308 created_target, target = _GetOrCreateTargetByName(targets, target_name) | 315 target = _GetOrCreateTargetByName(targets, target_name) |
309 if created_target: | 316 if target.visited: |
310 roots.add(target) | |
311 elif target.visited: | |
312 continue | 317 continue |
313 | 318 |
314 target.visited = True | 319 target.visited = True |
315 target.requires_build = _DoesTargetTypeRequireBuild( | 320 target.requires_build = _DoesTargetTypeRequireBuild( |
316 target_dicts[target_name]) | 321 target_dicts[target_name]) |
317 target_type = target_dicts[target_name]['type'] | 322 target_type = target_dicts[target_name]['type'] |
318 target.is_executable = target_type == 'executable' | 323 target.is_executable = target_type == 'executable' |
319 target.is_static_library = target_type == 'static_library' | 324 target.is_static_library = target_type == 'static_library' |
320 target.is_or_has_linked_ancestor = (target_type == 'executable' or | 325 target.is_or_has_linked_ancestor = (target_type == 'executable' or |
321 target_type == 'shared_library') | 326 target_type == 'shared_library') |
322 | 327 |
323 build_file = gyp.common.ParseQualifiedTarget(target_name)[0] | 328 build_file = gyp.common.ParseQualifiedTarget(target_name)[0] |
324 if not build_file in build_file_in_files: | 329 if not build_file in build_file_in_files: |
325 build_file_in_files[build_file] = \ | 330 build_file_in_files[build_file] = \ |
326 _WasBuildFileModified(build_file, data, files, toplevel_dir) | 331 _WasBuildFileModified(build_file, data, files, toplevel_dir) |
327 | 332 |
328 if build_file in build_files: | |
329 build_file_targets.add(target) | |
330 | |
331 # If a build file (or any of its included files) is modified we assume all | 333 # If a build file (or any of its included files) is modified we assume all |
332 # targets in the file are modified. | 334 # targets in the file are modified. |
333 if build_file_in_files[build_file]: | 335 if build_file_in_files[build_file]: |
334 print 'matching target from modified build file', target_name | 336 print 'matching target from modified build file', target_name |
335 target.match_status = MATCH_STATUS_MATCHES | 337 target.match_status = MATCH_STATUS_MATCHES |
336 matching_targets.append(target) | 338 matching_targets.append(target) |
337 else: | 339 else: |
338 sources = _ExtractSources(target_name, target_dicts[target_name], | 340 sources = _ExtractSources(target_name, target_dicts[target_name], |
339 toplevel_dir) | 341 toplevel_dir) |
340 for source in sources: | 342 for source in sources: |
341 if _ToGypPath(os.path.normpath(source)) in files: | 343 if _ToGypPath(os.path.normpath(source)) in files: |
342 print 'target', target_name, 'matches', source | 344 print 'target', target_name, 'matches', source |
343 target.match_status = MATCH_STATUS_MATCHES | 345 target.match_status = MATCH_STATUS_MATCHES |
344 matching_targets.append(target) | 346 matching_targets.append(target) |
345 break | 347 break |
346 | 348 |
347 # Add dependencies to visit as well as updating back pointers for deps. | 349 # Add dependencies to visit as well as updating back pointers for deps. |
348 for dep in target_dicts[target_name].get('dependencies', []): | 350 for dep in target_dicts[target_name].get('dependencies', []): |
349 targets_to_visit.append(dep) | 351 targets_to_visit.append(dep) |
350 | 352 |
351 created_dep_target, dep_target = _GetOrCreateTargetByName(targets, dep) | 353 dep_target = _GetOrCreateTargetByName(targets, dep) |
352 if not created_dep_target: | |
353 roots.discard(dep_target) | |
354 | 354 |
355 target.deps.add(dep_target) | 355 target.deps.add(dep_target) |
356 dep_target.back_deps.add(target) | 356 dep_target.back_deps.add(target) |
357 | 357 |
358 return targets, matching_targets, roots & build_file_targets | 358 return targets, matching_targets |
359 | 359 |
360 | 360 |
361 def _GetUnqualifiedToTargetMapping(all_targets, to_find): | 361 def _GetUnqualifiedToTargetMapping(all_targets, to_find): |
362 """Returns a mapping (dictionary) from unqualified name to Target for all the | 362 """Returns a mapping (dictionary) from unqualified name to Target for all the |
363 Targets in |to_find|.""" | 363 Targets in |to_find|.""" |
364 result = {} | 364 result = {} |
365 if not to_find: | 365 if not to_find: |
366 return result | 366 return result |
367 to_find = set(to_find) | 367 to_find = set(to_find) |
368 for target_name in all_targets.keys(): | 368 for target_name in all_targets.keys(): |
369 extracted = gyp.common.ParseQualifiedTarget(target_name) | 369 extracted = gyp.common.ParseQualifiedTarget(target_name) |
370 if len(extracted) > 1 and extracted[1] in to_find: | 370 if len(extracted) > 1 and extracted[1] in to_find: |
371 to_find.remove(extracted[1]) | 371 to_find.remove(extracted[1]) |
372 result[extracted[1]] = all_targets[target_name] | 372 result[extracted[1]] = all_targets[target_name] |
373 if not to_find: | 373 if not to_find: |
374 return result | 374 return result |
375 return result | 375 return result |
376 | 376 |
377 | 377 |
378 def _DoesTargetDependOn(target): | 378 def _AddBuildTargets(target, roots, result): |
379 """Returns true if |target| or any of its dependencies matches the supplied | |
380 set of paths. This updates |matches| of the Targets as it recurses. | |
381 target: the Target to look for.""" | |
382 if target.match_status == MATCH_STATUS_DOESNT_MATCH: | |
383 return False | |
384 if target.match_status == MATCH_STATUS_MATCHES or \ | |
385 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: | |
386 return True | |
387 for dep in target.deps: | |
388 if _DoesTargetDependOn(dep): | |
389 target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY | |
390 print '\t', target.name, 'matches by dep', dep.name | |
391 return True | |
392 target.match_status = MATCH_STATUS_DOESNT_MATCH | |
393 return False | |
394 | |
395 | |
396 def _GetTargetsDependingOn(possible_targets): | |
397 """Returns the list of Targets in |possible_targets| that depend (either | |
398 directly on indirectly) on the matched targets. | |
399 possible_targets: targets to search from.""" | |
400 found = [] | |
401 print 'Targets that matched by dependency:' | |
402 for target in possible_targets: | |
403 if _DoesTargetDependOn(target): | |
404 found.append(target) | |
405 return found | |
406 | |
407 | |
408 def _AddBuildTargets(target, roots, add_if_no_ancestor, result): | |
409 """Recurses through all targets that depend on |target|, adding all targets | 379 """Recurses through all targets that depend on |target|, adding all targets |
410 that need to be built (and are in |roots|) to |result|. | 380 that need to be built (and are in |roots|) to |result|. |
411 roots: set of root targets. | 381 roots: set of root targets. |
412 add_if_no_ancestor: If true and there are no ancestors of |target| then add | |
413 |target| to |result|. |target| must still be in |roots|. | |
414 result: targets that need to be built are added here.""" | 382 result: targets that need to be built are added here.""" |
415 if target.visited: | 383 if target.visited: |
416 return | 384 return |
417 | 385 |
418 target.visited = True | 386 target.visited = True |
419 target.in_roots = not target.back_deps and target in roots | 387 target.in_roots = target in roots |
420 | 388 |
421 for back_dep_target in target.back_deps: | 389 for back_dep_target in target.back_deps: |
422 _AddBuildTargets(back_dep_target, roots, False, result) | 390 _AddBuildTargets(back_dep_target, roots, result) |
423 target.added_to_compile_targets |= back_dep_target.added_to_compile_targets | 391 target.added_to_compile_targets |= back_dep_target.added_to_compile_targets |
424 target.in_roots |= back_dep_target.in_roots | 392 target.in_roots |= back_dep_target.in_roots |
425 target.is_or_has_linked_ancestor |= ( | 393 target.is_or_has_linked_ancestor |= ( |
426 back_dep_target.is_or_has_linked_ancestor) | 394 back_dep_target.is_or_has_linked_ancestor) |
427 | 395 |
428 # Always add 'executable' targets. Even though they may be built by other | 396 # Always add 'executable' targets. Even though they may be built by other |
429 # targets that depend upon them it makes detection of what is going to be | 397 # targets that depend upon them it makes detection of what is going to be |
430 # built easier. | 398 # built easier. |
431 # And always add static_libraries that have no dependencies on them from | 399 # And always add static_libraries that have no dependencies on them from |
432 # linkables. This is necessary as the other dependencies on them may be | 400 # linkables. This is necessary as the other dependencies on them may be |
433 # static libraries themselves, which are not compile time dependencies. | 401 # static libraries themselves, which are not compile time dependencies. |
434 if target.in_roots and \ | 402 if target.in_roots and \ |
435 (target.is_executable or | 403 (target.is_executable or |
436 (not target.added_to_compile_targets and | 404 (not target.added_to_compile_targets and target.requires_build) or |
437 (add_if_no_ancestor or target.requires_build)) or | 405 (target.is_static_library and not target.is_or_has_linked_ancestor)): |
438 (target.is_static_library and add_if_no_ancestor and | |
439 not target.is_or_has_linked_ancestor)): | |
440 print '\t\tadding to build targets', target.name, 'executable', \ | 406 print '\t\tadding to build targets', target.name, 'executable', \ |
441 target.is_executable, 'added_to_compile_targets', \ | 407 target.is_executable, 'added_to_compile_targets', \ |
442 target.added_to_compile_targets, 'add_if_no_ancestor', \ | 408 target.added_to_compile_targets, 'requires_build', \ |
443 add_if_no_ancestor, 'requires_build', target.requires_build, \ | 409 target.requires_build, 'is_static_library', \ |
444 'is_static_library', target.is_static_library, \ | 410 target.is_static_library, 'is_or_has_linked_ancestor', \ |
445 'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor | 411 target.is_or_has_linked_ancestor |
446 result.add(target) | 412 result.add(target) |
447 target.added_to_compile_targets = True | 413 target.added_to_compile_targets = True |
448 | 414 |
449 | 415 |
450 def _GetBuildTargets(matching_targets, roots): | 416 def _GetBuildTargets(matching_targets, roots): |
451 """Returns the set of Targets that require a build. | 417 """Returns the set of Targets that require a build. |
452 matching_targets: targets that changed and need to be built. | 418 matching_targets: targets that changed and need to be built. |
453 roots: set of root targets in the build files to search from.""" | 419 roots: set of root targets in the build files to search from.""" |
454 result = set() | 420 result = set() |
455 for target in matching_targets: | 421 for target in matching_targets: |
456 print '\tfinding build targets for match', target.name | 422 print '\tfinding build targets for match', target.name |
457 _AddBuildTargets(target, roots, True, result) | 423 _AddBuildTargets(target, roots, result) |
458 return result | 424 return result |
459 | 425 |
460 | 426 |
461 def _WriteOutput(params, **values): | 427 def _WriteOutput(params, **values): |
462 """Writes the output, either to stdout or a file is specified.""" | 428 """Writes the output, either to stdout or a file is specified.""" |
463 if 'error' in values: | 429 if 'error' in values: |
464 print 'Error:', values['error'] | 430 print 'Error:', values['error'] |
465 if 'status' in values: | 431 if 'status' in values: |
466 print values['status'] | 432 print values['status'] |
467 if 'targets' in values: | 433 if 'targets' in values: |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
550 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) | 516 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) |
551 if debug: | 517 if debug: |
552 print 'toplevel_dir', toplevel_dir | 518 print 'toplevel_dir', toplevel_dir |
553 | 519 |
554 if _WasGypIncludeFileModified(params, config.files): | 520 if _WasGypIncludeFileModified(params, config.files): |
555 result_dict = { 'status': all_changed_string, | 521 result_dict = { 'status': all_changed_string, |
556 'targets': list(config.targets) } | 522 'targets': list(config.targets) } |
557 _WriteOutput(params, **result_dict) | 523 _WriteOutput(params, **result_dict) |
558 return | 524 return |
559 | 525 |
560 all_targets, matching_targets, roots = _GenerateTargets( | 526 all_targets, matching_targets = _GenerateTargets( |
561 data, target_list, target_dicts, toplevel_dir, frozenset(config.files), | 527 data, target_list, target_dicts, toplevel_dir, frozenset(config.files), |
562 params['build_files']) | 528 params['build_files']) |
563 | 529 |
564 print 'roots:' | |
565 for root in roots: | |
566 print '\t', root.name | |
567 | |
568 unqualified_mapping = _GetUnqualifiedToTargetMapping(all_targets, | 530 unqualified_mapping = _GetUnqualifiedToTargetMapping(all_targets, |
569 config.targets) | 531 config.targets) |
570 invalid_targets = None | 532 invalid_targets = None |
571 if len(unqualified_mapping) != len(config.targets): | 533 if len(unqualified_mapping) != len(config.targets): |
572 invalid_targets = _NamesNotIn(config.targets, unqualified_mapping) | 534 invalid_targets = _NamesNotIn(config.targets, unqualified_mapping) |
573 | 535 |
574 if matching_targets: | 536 if matching_targets: |
575 search_targets = _LookupTargets(config.targets, unqualified_mapping) | 537 search_targets = _LookupTargets(config.targets, unqualified_mapping) |
576 print 'supplied targets' | 538 print 'supplied targets' |
577 for target in config.targets: | 539 for target in config.targets: |
578 print '\t', target | 540 print '\t', target |
579 print 'expanded supplied targets' | 541 print 'expanded supplied targets' |
580 for target in search_targets: | 542 for target in search_targets: |
581 print '\t', target.name | 543 print '\t', target.name |
582 matched_search_targets = _GetTargetsDependingOn(search_targets) | |
583 print 'raw matched search targets:' | |
584 for target in matched_search_targets: | |
585 print '\t', target.name | |
586 # Reset the visited status for _GetBuildTargets. | 544 # Reset the visited status for _GetBuildTargets. |
587 for target in all_targets.itervalues(): | 545 for target in all_targets.itervalues(): |
588 target.visited = False | 546 target.visited = False |
589 print 'Finding build targets' | 547 print 'Finding build targets' |
590 build_targets = _GetBuildTargets(matching_targets, roots) | 548 build_targets = _GetBuildTargets(matching_targets, search_targets) |
591 matched_search_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] | |
592 for target in matched_search_targets] | |
593 build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] | 549 build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] |
594 for target in build_targets] | 550 for target in build_targets] |
595 else: | 551 else: |
596 matched_search_targets = [] | |
597 build_targets = [] | 552 build_targets = [] |
598 | 553 |
599 result_dict = { 'targets': matched_search_targets, | 554 # TODO(sky): nuke 'targets'. |
| 555 result_dict = { 'targets': build_targets, |
600 'status': found_dependency_string if matching_targets else | 556 'status': found_dependency_string if matching_targets else |
601 no_dependency_string, | 557 no_dependency_string, |
602 'build_targets': build_targets} | 558 'build_targets': build_targets} |
603 if invalid_targets: | 559 if invalid_targets: |
604 result_dict['invalid_targets'] = invalid_targets | 560 result_dict['invalid_targets'] = invalid_targets |
605 _WriteOutput(params, **result_dict) | 561 _WriteOutput(params, **result_dict) |
606 | 562 |
607 except Exception as e: | 563 except Exception as e: |
608 _WriteOutput(params, error=str(e)) | 564 _WriteOutput(params, error=str(e)) |
OLD | NEW |