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

Source Code for Module SCons.Taskmaster

  1  # 
  2  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation 
  3  # 
  4  # Permission is hereby granted, free of charge, to any person obtaining 
  5  # a copy of this software and associated documentation files (the 
  6  # "Software"), to deal in the Software without restriction, including 
  7  # without limitation the rights to use, copy, modify, merge, publish, 
  8  # distribute, sublicense, and/or sell copies of the Software, and to 
  9  # permit persons to whom the Software is furnished to do so, subject to 
 10  # the following conditions: 
 11  # 
 12  # The above copyright notice and this permission notice shall be included 
 13  # in all copies or substantial portions of the Software. 
 14  # 
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 16  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 17  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 18  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 19  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 20  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 21  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 22  # 
 23   
 24  __doc__ = """ 
 25  Generic Taskmaster module for the SCons build engine. 
 26   
 27  This module contains the primary interface(s) between a wrapping user 
 28  interface and the SCons build engine.  There are two key classes here: 
 29   
 30      Taskmaster 
 31          This is the main engine for walking the dependency graph and 
 32          calling things to decide what does or doesn't need to be built. 
 33   
 34      Task 
 35          This is the base class for allowing a wrapping interface to 
 36          decide what does or doesn't actually need to be done.  The 
 37          intention is for a wrapping interface to subclass this as 
 38          appropriate for different types of behavior it may need. 
 39   
 40          The canonical example is the SCons native Python interface, 
 41          which has Task subclasses that handle its specific behavior, 
 42          like printing "`foo' is up to date" when a top-level target 
 43          doesn't need to be built, and handling the -c option by removing 
 44          targets as its "build" action.  There is also a separate subclass 
 45          for suppressing this output when the -q option is used. 
 46   
 47          The Taskmaster instantiates a Task object for each (set of) 
 48          target(s) that it decides need to be evaluated and/or built. 
 49  """ 
 50   
 51  __revision__ = "src/engine/SCons/Taskmaster.py 3795 2008/11/25 22:04:43 scons" 
 52   
 53  from itertools import chain 
 54  import operator 
 55  import string 
 56  import sys 
 57  import traceback 
 58   
 59  import SCons.Errors 
 60  import SCons.Node 
 61   
 62  StateString = SCons.Node.StateString 
 63  NODE_NO_STATE = SCons.Node.no_state 
 64  NODE_PENDING = SCons.Node.pending 
 65  NODE_EXECUTING = SCons.Node.executing 
 66  NODE_UP_TO_DATE = SCons.Node.up_to_date 
 67  NODE_EXECUTED = SCons.Node.executed 
 68  NODE_FAILED = SCons.Node.failed 
 69   
 70   
 71  # A subsystem for recording stats about how different Nodes are handled by 
 72  # the main Taskmaster loop.  There's no external control here (no need for 
 73  # a --debug= option); enable it by changing the value of CollectStats. 
 74   
 75  CollectStats = None 
 76   
77 -class Stats:
78 """ 79 A simple class for holding statistics about the disposition of a 80 Node by the Taskmaster. If we're collecting statistics, each Node 81 processed by the Taskmaster gets one of these attached, in which case 82 the Taskmaster records its decision each time it processes the Node. 83 (Ideally, that's just once per Node.) 84 """
85 - def __init__(self):
86 """ 87 Instantiates a Taskmaster.Stats object, initializing all 88 appropriate counters to zero. 89 """ 90 self.considered = 0 91 self.already_handled = 0 92 self.problem = 0 93 self.child_failed = 0 94 self.not_built = 0 95 self.side_effects = 0 96 self.build = 0
97 98 StatsNodes = [] 99 100 fmt = "%(considered)3d "\ 101 "%(already_handled)3d " \ 102 "%(problem)3d " \ 103 "%(child_failed)3d " \ 104 "%(not_built)3d " \ 105 "%(side_effects)3d " \ 106 "%(build)3d " 107
108 -def dump_stats():
109 StatsNodes.sort(lambda a, b: cmp(str(a), str(b))) 110 for n in StatsNodes: 111 print (fmt % n.stats.__dict__) + str(n)
112 113 114
115 -class Task:
116 """ 117 Default SCons build engine task. 118 119 This controls the interaction of the actual building of node 120 and the rest of the engine. 121 122 This is expected to handle all of the normally-customizable 123 aspects of controlling a build, so any given application 124 *should* be able to do what it wants by sub-classing this 125 class and overriding methods as appropriate. If an application 126 needs to customze something by sub-classing Taskmaster (or 127 some other build engine class), we should first try to migrate 128 that functionality into this class. 129 130 Note that it's generally a good idea for sub-classes to call 131 these methods explicitly to update state, etc., rather than 132 roll their own interaction with Taskmaster from scratch. 133 """
134 - def __init__(self, tm, targets, top, node):
135 self.tm = tm 136 self.targets = targets 137 self.top = top 138 self.node = node 139 self.exc_clear()
140
141 - def display(self, message):
142 """ 143 Hook to allow the calling interface to display a message. 144 145 This hook gets called as part of preparing a task for execution 146 (that is, a Node to be built). As part of figuring out what Node 147 should be built next, the actually target list may be altered, 148 along with a message describing the alteration. The calling 149 interface can subclass Task and provide a concrete implementation 150 of this method to see those messages. 151 """ 152 pass
153
154 - def prepare(self):
155 """ 156 Called just before the task is executed. 157 158 This is mainly intended to give the target Nodes a chance to 159 unlink underlying files and make all necessary directories before 160 the Action is actually called to build the targets. 161 """ 162 163 # Now that it's the appropriate time, give the TaskMaster a 164 # chance to raise any exceptions it encountered while preparing 165 # this task. 166 self.exception_raise() 167 168 if self.tm.message: 169 self.display(self.tm.message) 170 self.tm.message = None 171 172 # Let the targets take care of any necessary preparations. 173 # This includes verifying that all of the necessary sources 174 # and dependencies exist, removing the target file(s), etc. 175 # 176 # As of April 2008, the get_executor().prepare() method makes 177 # sure that all of the aggregate sources necessary to build this 178 # Task's target(s) exist in one up-front check. The individual 179 # target t.prepare() methods check that each target's explicit 180 # or implicit dependencies exists, and also initialize the 181 # .sconsign info. 182 self.targets[0].get_executor().prepare() 183 for t in self.targets: 184 t.prepare() 185 for s in t.side_effects: 186 s.prepare()
187
188 - def get_target(self):
189 """Fetch the target being built or updated by this task. 190 """ 191 return self.node
192
193 - def needs_execute(self):
194 """ 195 Called to determine whether the task's execute() method should 196 be run. 197 198 This method allows one to skip the somethat costly execution 199 of the execute() method in a seperate thread. For example, 200 that would be unnecessary for up-to-date targets. 201 """ 202 return True
203
204 - def execute(self):
205 """ 206 Called to execute the task. 207 208 This method is called from multiple threads in a parallel build, 209 so only do thread safe stuff here. Do thread unsafe stuff in 210 prepare(), executed() or failed(). 211 """ 212 213 try: 214 everything_was_cached = 1 215 for t in self.targets: 216 if not t.retrieve_from_cache(): 217 everything_was_cached = 0 218 break 219 if not everything_was_cached: 220 self.targets[0].build() 221 except SystemExit: 222 exc_value = sys.exc_info()[1] 223 raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code) 224 except SCons.Errors.UserError: 225 raise 226 except SCons.Errors.BuildError: 227 raise 228 except Exception, e: 229 buildError = SCons.Errors.convert_to_BuildError(e) 230 buildError.node = self.targets[0] 231 buildError.exc_info = sys.exc_info() 232 raise buildError
233
235 """ 236 Called when the task has been successfully executed 237 and the Taskmaster instance doesn't want to call 238 the Node's callback methods. 239 """ 240 for t in self.targets: 241 if t.get_state() == NODE_EXECUTING: 242 for side_effect in t.side_effects: 243 side_effect.set_state(NODE_NO_STATE) 244 t.set_state(NODE_EXECUTED)
245
246 - def executed_with_callbacks(self):
247 """ 248 Called when the task has been successfully executed and 249 the Taskmaster instance wants to call the Node's callback 250 methods. 251 252 This may have been a do-nothing operation (to preserve build 253 order), so we must check the node's state before deciding whether 254 it was "built", in which case we call the appropriate Node method. 255 In any event, we always call "visited()", which will handle any 256 post-visit actions that must take place regardless of whether 257 or not the target was an actual built target or a source Node. 258 """ 259 for t in self.targets: 260 if t.get_state() == NODE_EXECUTING: 261 for side_effect in t.side_effects: 262 side_effect.set_state(NODE_NO_STATE) 263 t.set_state(NODE_EXECUTED) 264 t.built() 265 t.visited()
266 267 executed = executed_with_callbacks 268
269 - def failed(self):
270 """ 271 Default action when a task fails: stop the build. 272 """ 273 self.fail_stop()
274
275 - def fail_stop(self):
276 """ 277 Explicit stop-the-build failure. 278 """ 279 280 # Invoke will_not_build() to clean-up the pending children 281 # list. 282 self.tm.will_not_build(self.targets) 283 284 # Tell the taskmaster to not start any new tasks 285 self.tm.stop() 286 287 # We're stopping because of a build failure, but give the 288 # calling Task class a chance to postprocess() the top-level 289 # target under which the build failure occurred. 290 self.targets = [self.tm.current_top] 291 self.top = 1
292
293 - def fail_continue(self):
294 """ 295 Explicit continue-the-build failure. 296 297 This sets failure status on the target nodes and all of 298 their dependent parent nodes. 299 """ 300 self.tm.will_not_build(self.targets)
301
302 - def make_ready_all(self):
303 """ 304 Marks all targets in a task ready for execution. 305 306 This is used when the interface needs every target Node to be 307 visited--the canonical example being the "scons -c" option. 308 """ 309 self.out_of_date = self.targets[:] 310 for t in self.targets: 311 t.disambiguate().set_state(NODE_EXECUTING) 312 for s in t.side_effects: 313 s.set_state(NODE_EXECUTING)
314
315 - def make_ready_current(self):
316 """ 317 Marks all targets in a task ready for execution if any target 318 is not current. 319 320 This is the default behavior for building only what's necessary. 321 """ 322 self.out_of_date = [] 323 needs_executing = False 324 for t in self.targets: 325 try: 326 t.disambiguate().make_ready() 327 is_up_to_date = not t.has_builder() or \ 328 (not t.always_build and t.is_up_to_date()) 329 except EnvironmentError, e: 330 raise SCons.Errors.BuildError(node=t, errstr=e.strerror, filename=e.filename) 331 332 if not is_up_to_date: 333 self.out_of_date.append(t) 334 needs_executing = True 335 336 if needs_executing: 337 for t in self.targets: 338 t.set_state(NODE_EXECUTING) 339 for s in t.side_effects: 340 s.set_state(NODE_EXECUTING) 341 else: 342 for t in self.targets: 343 # We must invoke visited() to ensure that the node 344 # information has been computed before allowing the 345 # parent nodes to execute. (That could occur in a 346 # parallel build...) 347 t.visited() 348 t.set_state(NODE_UP_TO_DATE)
349 350 make_ready = make_ready_current 351
352 - def postprocess(self):
353 """ 354 Post-processes a task after it's been executed. 355 356 This examines all the targets just built (or not, we don't care 357 if the build was successful, or even if there was no build 358 because everything was up-to-date) to see if they have any 359 waiting parent Nodes, or Nodes waiting on a common side effect, 360 that can be put back on the candidates list. 361 """ 362 363 # We may have built multiple targets, some of which may have 364 # common parents waiting for this build. Count up how many 365 # targets each parent was waiting for so we can subtract the 366 # values later, and so we *don't* put waiting side-effect Nodes 367 # back on the candidates list if the Node is also a waiting 368 # parent. 369 370 targets = set(self.targets) 371 372 parents = {} 373 for t in targets: 374 for p in t.waiting_parents: 375 parents[p] = parents.get(p, 0) + 1 376 377 for t in targets: 378 for s in t.side_effects: 379 if s.get_state() == NODE_EXECUTING: 380 s.set_state(NODE_NO_STATE) 381 for p in s.waiting_parents: 382 parents[p] = parents.get(p, 0) + 1 383 for p in s.waiting_s_e: 384 if p.ref_count == 0: 385 self.tm.candidates.append(p) 386 self.tm.pending_children.discard(p) 387 388 for p, subtract in parents.items(): 389 p.ref_count = p.ref_count - subtract 390 if p.ref_count == 0: 391 self.tm.candidates.append(p) 392 self.tm.pending_children.discard(p) 393 394 for t in targets: 395 t.postprocess()
396 397 # Exception handling subsystem. 398 # 399 # Exceptions that occur while walking the DAG or examining Nodes 400 # must be raised, but must be raised at an appropriate time and in 401 # a controlled manner so we can, if necessary, recover gracefully, 402 # possibly write out signature information for Nodes we've updated, 403 # etc. This is done by having the Taskmaster tell us about the 404 # exception, and letting 405
406 - def exc_info(self):
407 """ 408 Returns info about a recorded exception. 409 """ 410 return self.exception
411
412 - def exc_clear(self):
413 """ 414 Clears any recorded exception. 415 416 This also changes the "exception_raise" attribute to point 417 to the appropriate do-nothing method. 418 """ 419 self.exception = (None, None, None) 420 self.exception_raise = self._no_exception_to_raise
421
422 - def exception_set(self, exception=None):
423 """ 424 Records an exception to be raised at the appropriate time. 425 426 This also changes the "exception_raise" attribute to point 427 to the method that will, in fact 428 """ 429 if not exception: 430 exception = sys.exc_info() 431 self.exception = exception 432 self.exception_raise = self.