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