Package SCons :: Module Executor
[hide private]
[frames] | no frames]

Source Code for Module SCons.Executor

  1  """SCons.Executor 
  2   
  3  A module for executing actions with specific lists of target and source 
  4  Nodes. 
  5   
  6  """ 
  7   
  8  # 
  9  # Copyright (c) 2001 - 2016 The SCons Foundation 
 10  # 
 11  # Permission is hereby granted, free of charge, to any person obtaining 
 12  # a copy of this software and associated documentation files (the 
 13  # "Software"), to deal in the Software without restriction, including 
 14  # without limitation the rights to use, copy, modify, merge, publish, 
 15  # distribute, sublicense, and/or sell copies of the Software, and to 
 16  # permit persons to whom the Software is furnished to do so, subject to 
 17  # the following conditions: 
 18  # 
 19  # The above copyright notice and this permission notice shall be included 
 20  # in all copies or substantial portions of the Software. 
 21  # 
 22  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 23  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 24  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 25  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 26  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 27  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 28  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 29   
 30  __revision__ = "src/engine/SCons/Executor.py rel_2.5.0:3543:937e55cd78f7 2016/04/09 11:29:54 bdbaddog" 
 31   
 32  import collections 
 33   
 34  import SCons.Debug 
 35  from SCons.Debug import logInstanceCreation 
 36  import SCons.Errors 
 37  import SCons.Memoize 
38 39 40 -class Batch(object):
41 """Remembers exact association between targets 42 and sources of executor.""" 43 44 __slots__ = ('targets', 45 'sources') 46
47 - def __init__(self, targets=[], sources=[]):
48 self.targets = targets 49 self.sources = sources
50
51 52 53 -class TSList(collections.UserList):
54 """A class that implements $TARGETS or $SOURCES expansions by wrapping 55 an executor Method. This class is used in the Executor.lvars() 56 to delay creation of NodeList objects until they're needed. 57 58 Note that we subclass collections.UserList purely so that the 59 is_Sequence() function will identify an object of this class as 60 a list during variable expansion. We're not really using any 61 collections.UserList methods in practice. 62 """
63 - def __init__(self, func):
64 self.func = func
65 - def __getattr__(self, attr):
66 nl = self.func() 67 return getattr(nl, attr)
68 - def __getitem__(self, i):
69 nl = self.func() 70 return nl[i]
71 - def __getslice__(self, i, j):
72 nl = self.func() 73 i = max(i, 0); j = max(j, 0) 74 return nl[i:j]
75 - def __str__(self):
76 nl = self.func() 77 return str(nl)
78 - def __repr__(self):
79 nl = self.func() 80 return repr(nl)
81
82 -class TSObject(object):
83 """A class that implements $TARGET or $SOURCE expansions by wrapping 84 an Executor method. 85 """
86 - def __init__(self, func):
87 self.func = func
88 - def __getattr__(self, attr):
89 n = self.func() 90 return getattr(n, attr)
91 - def __str__(self):
92 n = self.func() 93 if n: 94 return str(n) 95 return ''
96 - def __repr__(self):
97 n = self.func() 98 if n: 99 return repr(n) 100 return ''
101
102 -def rfile(node):
103 """ 104 A function to return the results of a Node's rfile() method, 105 if it exists, and the Node itself otherwise (if it's a Value 106 Node, e.g.). 107 """ 108 try: 109 rfile = node.rfile 110 except AttributeError: 111 return node 112 else: 113 return rfile()
114
115 116 -def execute_nothing(obj, target, kw):
117 return 0
118
119 -def execute_action_list(obj, target, kw):
120 """Actually execute the action list.""" 121 env = obj.get_build_env() 122 kw = obj.get_kw(kw) 123 status = 0 124 for act in obj.get_action_list(): 125 args = ([], [], env) 126 status = act(*args, **kw) 127 if isinstance(status, SCons.Errors.BuildError): 128 status.executor = obj 129 raise status 130 elif status: 131 msg = "Error %s" % status 132 raise SCons.Errors.BuildError( 133 errstr=msg, 134 node=obj.batches[0].targets, 135 executor=obj, 136 action=act) 137 return status
138 139 _do_execute_map = {0 : execute_nothing, 140 1 : execute_action_list}
141 142 143 -def execute_actions_str(obj):
144 env = obj.get_build_env() 145 return "\n".join([action.genstring(obj.get_all_targets(), 146 obj.get_all_sources(), 147 env) 148 for action in obj.get_action_list()])
149
150 -def execute_null_str(obj):
151 return ''
152 153 _execute_str_map = {0 : execute_null_str, 154 1 : execute_actions_str}
155 156 157 -class Executor(object):
158 """A class for controlling instances of executing an action. 159 160 This largely exists to hold a single association of an action, 161 environment, list of environment override dictionaries, targets 162 and sources for later processing as needed. 163 """ 164 165 __slots__ = ('pre_actions', 166 'post_actions', 167 'env', 168 'overridelist', 169 'batches', 170 'builder_kw', 171 '_memo', 172 'lvars', 173 '_changed_sources_list', 174 '_changed_targets_list', 175 '_unchanged_sources_list', 176 '_unchanged_targets_list', 177 'action_list', 178 '_do_execute', 179 '_execute_str') 180
181 - def __init__(self, action, env=None, overridelist=[{}], 182 targets=[], sources=[], builder_kw={}):
183 if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Executor') 184 self.set_action_list(action) 185 self.pre_actions = [] 186 self.post_actions = [] 187 self.env = env 188 self.overridelist = overridelist 189 if targets or sources: 190 self.batches = [Batch(targets[:], sources[:])] 191 else: 192 self.batches = [] 193 self.builder_kw = builder_kw 194 self._do_execute = 1 195 self._execute_str = 1 196 self._memo = {}
197
198 - def get_lvars(self):
199 try: 200 return self.lvars 201 except AttributeError: 202 self.lvars = { 203 'CHANGED_SOURCES' : TSList(self._get_changed_sources), 204 'CHANGED_TARGETS' : TSList(self._get_changed_targets), 205 'SOURCE' : TSObject(self._get_source), 206 'SOURCES' : TSList(self._get_sources), 207 'TARGET' : TSObject(self._get_target), 208 'TARGETS' : TSList(self._get_targets), 209 'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources), 210 'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets), 211 } 212 return self.lvars
213
214 - def _get_changes(self):
215 cs = [] 216 ct = [] 217 us = [] 218 ut = [] 219 for b in self.batches: 220 # don't add targets marked always build to unchanged lists 221 # add to changed list as they always need to build 222 if not b.targets[0].always_build and b.targets[0].is_up_to_date(): 223 us.extend(list(map(rfile, b.sources))) 224 ut.extend(b.targets) 225 else: 226 cs.extend(list(map(rfile, b.sources))) 227 ct.extend(b.targets) 228 self._changed_sources_list = SCons.Util.NodeList(cs) 229 self._changed_targets_list = SCons.Util.NodeList(ct) 230 self._unchanged_sources_list = SCons.Util.NodeList(us) 231 self._unchanged_targets_list = SCons.Util.NodeList(ut)
232
233 - def _get_changed_sources(self, *args, **kw):
234 try: 235 return self._changed_sources_list 236 except AttributeError: 237 self._get_changes() 238 return self._changed_sources_list
239
240 - def _get_changed_targets(self, *args, **kw):
241 try: 242 return self._changed_targets_list 243 except AttributeError: 244 self._get_changes() 245 return self._changed_targets_list
246
247 - def _get_source(self, *args, **kw):
248 return rfile(self.batches[0].sources[0]).get_subst_proxy()
249
250 - def _get_sources(self, *args, **kw):
251 return SCons.Util.NodeList([rfile(n).get_subst_proxy() for n in self.get_all_sources()])
252
253 - def _get_target(self, *args, **kw):
254 return self.batches[0].targets[0].get_subst_proxy()
255
256 - def _get_targets(self, *args, **kw):
257 return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
258
259 - def _get_unchanged_sources(self, *args, **kw):
260 try: 261 return self._unchanged_sources_list 262 except AttributeError: 263 self._get_changes() 264 return self._unchanged_sources_list
265
266 - def _get_unchanged_targets(self, *args, **kw):
267 try: 268 return self._unchanged_targets_list 269 except AttributeError: 270 self._get_changes() 271 return self._unchanged_targets_list
272
273 - def get_action_targets(self):
274 if not self.action_list: 275 return [] 276 targets_string = self.action_list[0].get_targets(self.env, self) 277 if targets_string[0] == '$': 278 targets_string = targets_string[1:] 279 return self.get_lvars()[targets_string]
280
281 - def set_action_list(self, action):
282 import SCons.Util 283 if not SCons.Util.is_List(action): 284 if not action: 285 import SCons.Errors 286 raise SCons.Errors.UserError("Executor must have an action.") 287 action = [action] 288 self.action_list = action
289
290 - def get_action_list(self):
291 if self.action_list is None: 292 return [] 293 return self.pre_actions + self.action_list + self.post_actions
294
295 - def get_all_targets(self):
296 """Returns all targets for all batches of this Executor.""" 297 result = [] 298 for batch in self.batches: 299 result.extend(batch.targets) 300 return result
301
302 - def get_all_sources(self):
303 """Returns all sources for all batches of this Executor.""" 304 result = [] 305 for batch in self.batches: 306 result.extend(batch.sources) 307 return result
308
309 - def get_all_children(self):
310 """Returns all unique children (dependencies) for all batches 311 of this Executor. 312 313 The Taskmaster can recognize when it's already evaluated a 314 Node, so we don't have to make this list unique for its intended 315 canonical use case, but we expect there to be a lot of redundancy 316 (long lists of batched .cc files #including the same .h files 317 over and over), so removing the duplicates once up front should 318 save the Taskmaster a lot of work. 319 """ 320 result = SCons.Util.UniqueList([]) 321 for target in self.get_all_targets(): 322 result.extend(target.children()) 323 return result
324
325 - def get_all_prerequisites(self):
326 """Returns all unique (order-only) prerequisites for all batches 327 of this Executor. 328 """ 329 result = SCons.Util.UniqueList([]) 330 for target in self.get_all_targets(): 331 if target.prerequisites is not None: 332 result.extend(target.prerequisites) 333 return result
334
335 - def get_action_side_effects(self):
336 337 """Returns all side effects for all batches of this 338 Executor used by the underlying Action. 339 """ 340 result = SCons.Util.UniqueList([]) 341 for target in self.get_action_targets(): 342 result.extend(target.side_effects) 343 return result
344 345 @SCons.Memoize.CountMethodCall
346 - def get_build_env(self):
347 """Fetch or create the appropriate build Environment 348 for this Executor. 349 """ 350 try: 351 return self._memo['get_build_env'] 352 except KeyError: 353 pass 354 355 # Create the build environment instance with appropriate 356 # overrides. These get evaluated against the current 357 # environment's construction variables so that users can 358 # add to existing values by referencing the variable in 359 # the expansion. 360 overrides = {} 361 for odict in self.overridelist: 362 overrides.update(odict) 363 364 import SCons.Defaults 365 env = self.env or SCons.Defaults.DefaultEnvironment() 366 build_env = env.Override(overrides) 367 368 self._memo['get_build_env'] = build_env 369 370 return build_env
371
372 - def get_build_scanner_path(self, scanner):
373 """Fetch the scanner path for this executor's targets and sources. 374 """ 375 env = self.get_build_env() 376 try: 377 cwd = self.batches[0].targets[0].cwd 378 except (IndexError, AttributeError): 379 cwd = None 380 return scanner.path(env, cwd, 381 self.get_all_targets(), 382 self.get_all_sources())
383
384 - def get_kw(self, kw={}):
385 result = self.builder_kw.copy() 386 result.update(kw) 387 result['executor'] = self 388 return result
389 390 # use extra indirection because with new-style objects (Python 2.2 391 # and above) we can't override special methods, and nullify() needs 392 # to be able to do this. 393
394 - def __call__(self, target, **kw):
395 return _do_execute_map[self._do_execute](self, target, kw)
396
397 - def cleanup(self):
398 self._memo = {}
399
400 - def add_sources(self, sources):
401 """Add source files to this Executor's list. This is necessary 402 for "multi" Builders that can be called repeatedly to build up 403 a source file list for a given target.""" 404 # TODO(batch): extend to multiple batches 405 assert (len(self.batches) == 1) 406 # TODO(batch): remove duplicates? 407 sources = [x for x in sources if x not in self.batches[0].sources] 408 self.batches[0].sources.extend(sources)
409
410 - def get_sources(self):
411 return self.batches[0].sources
412
413 - def add_batch(self, targets, sources):
414 """Add pair of associated target and source to this Executor's list. 415 This is necessary for "batch" Builders that can be called repeatedly 416 to build up a list of matching target and source files that will be 417 used in order to update multiple target files at once from multiple 418 corresponding source files, for tools like MSVC that support it.""" 419 self.batches.append(Batch(targets, sources))
420
421 - def prepare(self):
422 """ 423 Preparatory checks for whether this Executor can go ahead 424 and (try to) build its targets. 425 """ 426 for s in self.get_all_sources(): 427 if s.missing(): 428 msg = "Source `%s' not found, needed by target `%s'." 429 raise SCons.Errors.StopError(msg % (s, self.batches[0].targets[0]))
430
431 - def add_pre_action(self, action):
432 self.pre_actions.append(action)
433
434 - def add_post_action(self, action):
435 self.post_actions.append(action)
436 437 # another extra indirection for new-style objects and nullify... 438
439 - def __str__(self):
440 return _execute_str_map[self._execute_str](self)
441
442 - def nullify(self):
443 self.cleanup() 444 self._do_execute = 0 445 self._execute_str = 0
446 447 @SCons.Memoize.CountMethodCall
448 - def get_contents(self):
449 """Fetch the signature contents. This is the main reason this 450 class exists, so we can compute this once and cache it regardless 451 of how many target or source Nodes there are. 452 """ 453 try: 454 return self._memo['get_contents'] 455 except KeyError: 456 pass 457 env = self.get_build_env() 458 result = "".join([action.get_contents(self.get_all_targets(), 459 self.get_all_sources(), 460 env) 461 for action in self.get_action_list()]) 462 self._memo['get_contents'] = result 463 return result
464
465 - def get_timestamp(self):
466 """Fetch a time stamp for this Executor. We don't have one, of 467 course (only files do), but this is the interface used by the 468 timestamp module. 469 """ 470 return 0
471
472 - def scan_targets(self, scanner):
473 # TODO(batch): scan by batches 474 self.scan(scanner, self.get_all_targets())
475
476 - def scan_sources(self, scanner):
477 # TODO(batch): scan by batches 478 if self.batches[0].sources: 479 self.scan(scanner, self.get_all_sources())
480
481 - def scan(self, scanner, node_list):
482 """Scan a list of this Executor's files (targets or sources) for 483 implicit dependencies and update all of the targets with them. 484 This essentially short-circuits an N*M scan of the sources for 485 each individual target, which is a hell of a lot more efficient. 486 """ 487 env = self.get_build_env() 488 path = self.get_build_scanner_path 489 kw = self.get_kw() 490 491 # TODO(batch): scan by batches) 492 deps = [] 493 494 for node in node_list: 495 node.disambiguate() 496 deps.extend(node.get_implicit_deps(env, scanner, path, kw)) 497 498 deps.extend(self.get_implicit_deps()) 499 500 for tgt in self.get_all_targets(): 501 tgt.add_to_implicit(deps)
502
503 - def _get_unignored_sources_key(self, node, ignore=()):
504 return (node,) + tuple(ignore)
505 506 @SCons.Memoize.CountDictCall(_get_unignored_sources_key)
507 - def get_unignored_sources(self, node, ignore=()):
508 key = (node,) + tuple(ignore) 509 try: 510 memo_dict = self._memo['get_unignored_sources'] 511 except KeyError: 512 memo_dict = {} 513 self._memo['get_unignored_sources'] = memo_dict 514 else: 515 try: 516 return memo_dict[key] 517 except KeyError: 518 pass 519 520 if node: 521 # TODO: better way to do this (it's a linear search, 522 # but it may not be critical path)? 523 sourcelist = [] 524 for b in self.batches: 525 if node in b.targets: 526 sourcelist = b.sources 527 break 528 else: 529 sourcelist = self.get_all_sources() 530 if ignore: 531 idict = {} 532 for i in ignore: 533 idict[i] = 1 534 sourcelist = [s for s in sourcelist if s not in idict] 535 536 memo_dict[key] = sourcelist 537 538 return sourcelist
539
540 - def get_implicit_deps(self):
541 """Return the executor's implicit dependencies, i.e. the nodes of 542 the commands to be executed.""" 543 result = [] 544 build_env = self.get_build_env() 545 for act in self.get_action_list(): 546 deps = act.get_implicit_deps(self.get_all_targets(), 547 self.get_all_sources(), 548 build_env) 549 result.extend(deps) 550 return result
551 552 553 554 _batch_executors = {}
555 556 -def GetBatchExecutor(key):
557 return _batch_executors[key]
558
559 -def AddBatchExecutor(key, executor):
560 assert key not in _batch_executors 561 _batch_executors[key] = executor
562 563 nullenv = None 564 565 566 import SCons.Util
567 -class NullEnvironment(SCons.Util.Null):
568 import SCons.CacheDir 569 _CacheDir_path = None 570 _CacheDir = SCons.CacheDir.CacheDir(None)
571 - def get_CacheDir(self):
572 return self._CacheDir
573
574 575 -def get_NullEnvironment():
576 """Use singleton pattern for Null Environments.""" 577 global nullenv 578 579 if nullenv is None: 580 nullenv = NullEnvironment() 581 return nullenv
582
583 -class Null(object):
584 """A null Executor, with a null build Environment, that does 585 nothing when the rest of the methods call it. 586 587 This might be able to disappear when we refactor things to 588 disassociate Builders from Nodes entirely, so we're not 589 going to worry about unit tests for this--at least for now. 590 """ 591 592 __slots__ = ('pre_actions', 593 'post_actions', 594 'env', 595 'overridelist', 596 'batches', 597 'builder_kw', 598 '_memo', 599 'lvars', 600 '_changed_sources_list', 601 '_changed_targets_list', 602 '_unchanged_sources_list', 603 '_unchanged_targets_list', 604 'action_list', 605 '_do_execute', 606 '_execute_str') 607
608 - def __init__(self, *args, **kw):
609 if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Null') 610 self.batches = [Batch(kw['targets'][:], [])]
611 - def get_build_env(self):
612 return get_NullEnvironment()
613 - def get_build_scanner_path(self):
614 return None
615 - def cleanup(self):
616 pass
617 - def prepare(self):
618 pass
619 - def get_unignored_sources(self, *args, **kw):
620 return tuple(())
621 - def get_action_targets(self):
622 return []
623 - def get_action_list(self):
624 return []
625 - def get_all_targets(self):
626 return self.batches[0].targets
627 - def get_all_sources(self):
628 return self.batches[0].targets[0].sources
629 - def get_all_children(self):
630 return self.batches[0].targets[0].children()
631 - def get_all_prerequisites(self):
632 return []
633 - def get_action_side_effects(self):
634 return []
635 - def __call__(self, *args, **kw):
636 return 0
637 - def get_contents(self):
638 return ''
639 - def _morph(self):
640 """Morph this Null executor to a real Executor object.""" 641 batches = self.batches 642 self.__class__ = Executor 643 self.__init__([]) 644 self.batches = batches
645 646 # The following methods require morphing this Null Executor to a 647 # real Executor object. 648
649 - def add_pre_action(self, action):
650 self._morph() 651 self.add_pre_action(action)
652 - def add_post_action(self, action):
653 self._morph() 654 self.add_post_action(action)
655 - def set_action_list(self, action):
656 self._morph() 657 self.set_action_list(action)
658 659 # Local Variables: 660 # tab-width:4 661 # indent-tabs-mode:nil 662 # End: 663 # vim: set expandtab tabstop=4 shiftwidth=4: 664