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

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