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

Side by Side Diff: pylib/gyp/generator/analyzer.py

Issue 1402813002: Change analyzer to search for build_targets from supplied targets (Closed) Base URL: https://chromium.googlesource.com/external/gyp@master
Patch Set: make targets same as build_targets Created 5 years, 2 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
« no previous file with comments | « no previous file | test/analyzer/gyptest-analyzer.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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))
OLDNEW
« no previous file with comments | « no previous file | test/analyzer/gyptest-analyzer.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698