1b54a54124bc3cfca310c1ab7dff415a5473bbed
[pd-aubio.git] / waflib / Task.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # WARNING! Do not edit! http://waf.googlecode.com/git/docs/wafbook/single.html#_obtaining_the_waf_file
4
5 import os,shutil,re,tempfile
6 from waflib import Utils,Logs,Errors
7 NOT_RUN=0
8 MISSING=1
9 CRASHED=2
10 EXCEPTION=3
11 SKIPPED=8
12 SUCCESS=9
13 ASK_LATER=-1
14 SKIP_ME=-2
15 RUN_ME=-3
16 COMPILE_TEMPLATE_SHELL='''
17 def f(tsk):
18         env = tsk.env
19         gen = tsk.generator
20         bld = gen.bld
21         wd = getattr(tsk, 'cwd', None)
22         p = env.get_flat
23         tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
24         return tsk.exec_command(cmd, cwd=wd, env=env.env or None)
25 '''
26 COMPILE_TEMPLATE_NOSHELL='''
27 def f(tsk):
28         env = tsk.env
29         gen = tsk.generator
30         bld = gen.bld
31         wd = getattr(tsk, 'cwd', None)
32         def to_list(xx):
33                 if isinstance(xx, str): return [xx]
34                 return xx
35         tsk.last_cmd = lst = []
36         %s
37         lst = [x for x in lst if x]
38         return tsk.exec_command(lst, cwd=wd, env=env.env or None)
39 '''
40 def cache_outputs(cls):
41         m1=cls.run
42         def run(self):
43                 bld=self.generator.bld
44                 if bld.cache_global and not bld.nocache:
45                         if self.can_retrieve_cache():
46                                 return 0
47                 return m1(self)
48         cls.run=run
49         m2=cls.post_run
50         def post_run(self):
51                 bld=self.generator.bld
52                 ret=m2(self)
53                 if bld.cache_global and not bld.nocache:
54                         self.put_files_cache()
55                 return ret
56         cls.post_run=post_run
57         return cls
58 classes={}
59 class store_task_type(type):
60         def __init__(cls,name,bases,dict):
61                 super(store_task_type,cls).__init__(name,bases,dict)
62                 name=cls.__name__
63                 if name.endswith('_task'):
64                         name=name.replace('_task','')
65                 if name!='evil'and name!='TaskBase':
66                         global classes
67                         if getattr(cls,'run_str',None):
68                                 (f,dvars)=compile_fun(cls.run_str,cls.shell)
69                                 cls.hcode=cls.run_str
70                                 cls.run_str=None
71                                 cls.run=f
72                                 cls.vars=list(set(cls.vars+dvars))
73                                 cls.vars.sort()
74                         elif getattr(cls,'run',None)and not'hcode'in cls.__dict__:
75                                 cls.hcode=Utils.h_fun(cls.run)
76                         if not getattr(cls,'nocache',None):
77                                 cls=cache_outputs(cls)
78                         getattr(cls,'register',classes)[name]=cls
79 evil=store_task_type('evil',(object,),{})
80 class TaskBase(evil):
81         color='GREEN'
82         ext_in=[]
83         ext_out=[]
84         before=[]
85         after=[]
86         hcode=''
87         def __init__(self,*k,**kw):
88                 self.hasrun=NOT_RUN
89                 try:
90                         self.generator=kw['generator']
91                 except KeyError:
92                         self.generator=self
93         def __repr__(self):
94                 return'\n\t{task %r: %s %s}'%(self.__class__.__name__,id(self),str(getattr(self,'fun','')))
95         def __str__(self):
96                 if hasattr(self,'fun'):
97                         return'executing: %s\n'%self.fun.__name__
98                 return self.__class__.__name__+'\n'
99         def __hash__(self):
100                 return id(self)
101         def exec_command(self,cmd,**kw):
102                 bld=self.generator.bld
103                 try:
104                         if not kw.get('cwd',None):
105                                 kw['cwd']=bld.cwd
106                 except AttributeError:
107                         bld.cwd=kw['cwd']=bld.variant_dir
108                 return bld.exec_command(cmd,**kw)
109         def runnable_status(self):
110                 return RUN_ME
111         def process(self):
112                 m=self.master
113                 if m.stop:
114                         m.out.put(self)
115                         return
116                 try:
117                         del self.generator.bld.task_sigs[self.uid()]
118                 except KeyError:
119                         pass
120                 try:
121                         self.generator.bld.returned_tasks.append(self)
122                         self.log_display(self.generator.bld)
123                         ret=self.run()
124                 except Exception:
125                         self.err_msg=Utils.ex_stack()
126                         self.hasrun=EXCEPTION
127                         m.error_handler(self)
128                         m.out.put(self)
129                         return
130                 if ret:
131                         self.err_code=ret
132                         self.hasrun=CRASHED
133                 else:
134                         try:
135                                 self.post_run()
136                         except Errors.WafError:
137                                 pass
138                         except Exception:
139                                 self.err_msg=Utils.ex_stack()
140                                 self.hasrun=EXCEPTION
141                         else:
142                                 self.hasrun=SUCCESS
143                 if self.hasrun!=SUCCESS:
144                         m.error_handler(self)
145                 m.out.put(self)
146         def run(self):
147                 if hasattr(self,'fun'):
148                         return self.fun(self)
149                 return 0
150         def post_run(self):
151                 pass
152         def log_display(self,bld):
153                 bld.to_log(self.display())
154         def display(self):
155                 col1=Logs.colors(self.color)
156                 col2=Logs.colors.NORMAL
157                 master=self.master
158                 def cur():
159                         tmp=-1
160                         if hasattr(master,'ready'):
161                                 tmp-=master.ready.qsize()
162                         return master.processed+tmp
163                 if self.generator.bld.progress_bar==1:
164                         return self.generator.bld.progress_line(cur(),master.total,col1,col2)
165                 if self.generator.bld.progress_bar==2:
166                         ela=str(self.generator.bld.timer)
167                         try:
168                                 ins=','.join([n.name for n in self.inputs])
169                         except AttributeError:
170                                 ins=''
171                         try:
172                                 outs=','.join([n.name for n in self.outputs])
173                         except AttributeError:
174                                 outs=''
175                         return'|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n'%(master.total,cur(),ins,outs,ela)
176                 s=str(self)
177                 if not s:
178                         return None
179                 total=master.total
180                 n=len(str(total))
181                 fs='[%%%dd/%%%dd] %%s%%s%%s'%(n,n)
182                 return fs%(cur(),total,col1,s,col2)
183         def attr(self,att,default=None):
184                 ret=getattr(self,att,self)
185                 if ret is self:return getattr(self.__class__,att,default)
186                 return ret
187         def hash_constraints(self):
188                 cls=self.__class__
189                 tup=(str(cls.before),str(cls.after),str(cls.ext_in),str(cls.ext_out),cls.__name__,cls.hcode)
190                 h=hash(tup)
191                 return h
192         def format_error(self):
193                 msg=getattr(self,'last_cmd','')
194                 name=getattr(self.generator,'name','')
195                 if getattr(self,"err_msg",None):
196                         return self.err_msg
197                 elif not self.hasrun:
198                         return'task in %r was not executed for some reason: %r'%(name,self)
199                 elif self.hasrun==CRASHED:
200                         try:
201                                 return' -> task in %r failed (exit status %r): %r\n%r'%(name,self.err_code,self,msg)
202                         except AttributeError:
203                                 return' -> task in %r failed: %r\n%r'%(name,self,msg)
204                 elif self.hasrun==MISSING:
205                         return' -> missing files in %r: %r\n%r'%(name,self,msg)
206                 else:
207                         return'invalid status for task in %r: %r'%(name,self.hasrun)
208         def colon(self,var1,var2):
209                 tmp=self.env[var1]
210                 if isinstance(var2,str):
211                         it=self.env[var2]
212                 else:
213                         it=var2
214                 if isinstance(tmp,str):
215                         return[tmp%x for x in it]
216                 else:
217                         if Logs.verbose and not tmp and it:
218                                 Logs.warn('Missing env variable %r for task %r (generator %r)'%(var1,self,self.generator))
219                         lst=[]
220                         for y in it:
221                                 lst.extend(tmp)
222                                 lst.append(y)
223                         return lst
224 class Task(TaskBase):
225         vars=[]
226         shell=False
227         def __init__(self,*k,**kw):
228                 TaskBase.__init__(self,*k,**kw)
229                 self.env=kw['env']
230                 self.inputs=[]
231                 self.outputs=[]
232                 self.dep_nodes=[]
233                 self.run_after=set([])
234         def __str__(self):
235                 env=self.env
236                 src_str=' '.join([a.nice_path()for a in self.inputs])
237                 tgt_str=' '.join([a.nice_path()for a in self.outputs])
238                 if self.outputs:sep=' -> '
239                 else:sep=''
240                 return'%s: %s%s%s\n'%(self.__class__.__name__.replace('_task',''),src_str,sep,tgt_str)
241         def __repr__(self):
242                 try:
243                         ins=",".join([x.name for x in self.inputs])
244                         outs=",".join([x.name for x in self.outputs])
245                 except AttributeError:
246                         ins=",".join([str(x)for x in self.inputs])
247                         outs=",".join([str(x)for x in self.outputs])
248                 return"".join(['\n\t{task %r: '%id(self),self.__class__.__name__," ",ins," -> ",outs,'}'])
249         def uid(self):
250                 try:
251                         return self.uid_
252                 except AttributeError:
253                         m=Utils.md5()
254                         up=m.update
255                         up(self.__class__.__name__)
256                         for x in self.inputs+self.outputs:
257                                 up(x.abspath())
258                         self.uid_=m.digest()
259                         return self.uid_
260         def set_inputs(self,inp):
261                 if isinstance(inp,list):self.inputs+=inp
262                 else:self.inputs.append(inp)
263         def set_outputs(self,out):
264                 if isinstance(out,list):self.outputs+=out
265                 else:self.outputs.append(out)
266         def set_run_after(self,task):
267                 assert isinstance(task,TaskBase)
268                 self.run_after.add(task)
269         def signature(self):
270                 try:return self.cache_sig
271                 except AttributeError:pass
272                 self.m=Utils.md5()
273                 self.m.update(self.hcode)
274                 self.sig_explicit_deps()
275                 self.sig_vars()
276                 if self.scan:
277                         try:
278                                 self.sig_implicit_deps()
279                         except Errors.TaskRescan:
280                                 return self.signature()
281                 ret=self.cache_sig=self.m.digest()
282                 return ret
283         def runnable_status(self):
284                 for t in self.run_after:
285                         if not t.hasrun:
286                                 return ASK_LATER
287                 bld=self.generator.bld
288                 try:
289                         new_sig=self.signature()
290                 except Errors.TaskNotReady:
291                         return ASK_LATER
292                 key=self.uid()
293                 try:
294                         prev_sig=bld.task_sigs[key]
295                 except KeyError:
296                         Logs.debug("task: task %r must run as it was never run before or the task code changed"%self)
297                         return RUN_ME
298                 for node in self.outputs:
299                         try:
300                                 if node.sig!=new_sig:
301                                         return RUN_ME
302                         except AttributeError:
303                                 Logs.debug("task: task %r must run as the output nodes do not exist"%self)
304                                 return RUN_ME
305                 if new_sig!=prev_sig:
306                         return RUN_ME
307                 return SKIP_ME
308         def post_run(self):
309                 bld=self.generator.bld
310                 sig=self.signature()
311                 for node in self.outputs:
312                         try:
313                                 os.stat(node.abspath())
314                         except OSError:
315                                 self.hasrun=MISSING
316                                 self.err_msg='-> missing file: %r'%node.abspath()
317                                 raise Errors.WafError(self.err_msg)
318                         node.sig=sig
319                 bld.task_sigs[self.uid()]=self.cache_sig
320         def sig_explicit_deps(self):
321                 bld=self.generator.bld
322                 upd=self.m.update
323                 for x in self.inputs+self.dep_nodes:
324                         try:
325                                 upd(x.get_bld_sig())
326                         except(AttributeError,TypeError):
327                                 raise Errors.WafError('Missing node signature for %r (required by %r)'%(x,self))
328                 if bld.deps_man:
329                         additional_deps=bld.deps_man
330                         for x in self.inputs+self.outputs:
331                                 try:
332                                         d=additional_deps[id(x)]
333                                 except KeyError:
334                                         continue
335                                 for v in d:
336                                         if isinstance(v,bld.root.__class__):
337                                                 try:
338                                                         v=v.get_bld_sig()
339                                                 except AttributeError:
340                                                         raise Errors.WafError('Missing node signature for %r (required by %r)'%(v,self))
341                                         elif hasattr(v,'__call__'):
342                                                 v=v()
343                                         upd(v)
344                 return self.m.digest()
345         def sig_vars(self):
346                 bld=self.generator.bld
347                 env=self.env
348                 upd=self.m.update
349                 act_sig=bld.hash_env_vars(env,self.__class__.vars)
350                 upd(act_sig)
351                 dep_vars=getattr(self,'dep_vars',None)
352                 if dep_vars:
353                         upd(bld.hash_env_vars(env,dep_vars))
354                 return self.m.digest()
355         scan=None
356         def sig_implicit_deps(self):
357                 bld=self.generator.bld
358                 key=self.uid()
359                 prev=bld.task_sigs.get((key,'imp'),[])
360                 if prev:
361                         try:
362                                 if prev==self.compute_sig_implicit_deps():
363                                         return prev
364                         except Exception:
365                                 for x in bld.node_deps.get(self.uid(),[]):
366                                         if x.is_child_of(bld.srcnode):
367                                                 try:
368                                                         os.stat(x.abspath())
369                                                 except OSError:
370                                                         try:
371                                                                 del x.parent.children[x.name]
372                                                         except KeyError:
373                                                                 pass
374                         del bld.task_sigs[(key,'imp')]
375                         raise Errors.TaskRescan('rescan')
376                 (nodes,names)=self.scan()
377                 if Logs.verbose:
378                         Logs.debug('deps: scanner for %s returned %s %s'%(str(self),str(nodes),str(names)))
379                 bld.node_deps[key]=nodes
380                 bld.raw_deps[key]=names
381                 self.are_implicit_nodes_ready()
382                 try:
383                         bld.task_sigs[(key,'imp')]=sig=self.compute_sig_implicit_deps()
384                 except Exception:
385                         if Logs.verbose:
386                                 for k in bld.node_deps.get(self.uid(),[]):
387                                         try:
388                                                 k.get_bld_sig()
389                                         except Exception:
390                                                 Logs.warn('Missing signature for node %r (may cause rebuilds)'%k)
391                 else:
392                         return sig
393         def compute_sig_implicit_deps(self):
394                 upd=self.m.update
395                 bld=self.generator.bld
396                 self.are_implicit_nodes_ready()
397                 for k in bld.node_deps.get(self.uid(),[]):
398                         upd(k.get_bld_sig())
399                 return self.m.digest()
400         def are_implicit_nodes_ready(self):
401                 bld=self.generator.bld
402                 try:
403                         cache=bld.dct_implicit_nodes
404                 except AttributeError:
405                         bld.dct_implicit_nodes=cache={}
406                 try:
407                         dct=cache[bld.cur]
408                 except KeyError:
409                         dct=cache[bld.cur]={}
410                         for tsk in bld.cur_tasks:
411                                 for x in tsk.outputs:
412                                         dct[x]=tsk
413                 modified=False
414                 for x in bld.node_deps.get(self.uid(),[]):
415                         if x in dct:
416                                 self.run_after.add(dct[x])
417                                 modified=True
418                 if modified:
419                         for tsk in self.run_after:
420                                 if not tsk.hasrun:
421                                         raise Errors.TaskNotReady('not ready')
422         def can_retrieve_cache(self):
423                 if not getattr(self,'outputs',None):
424                         return None
425                 sig=self.signature()
426                 ssig=Utils.to_hex(self.uid())+Utils.to_hex(sig)
427                 dname=os.path.join(self.generator.bld.cache_global,ssig)
428                 try:
429                         t1=os.stat(dname).st_mtime
430                 except OSError:
431                         return None
432                 for node in self.outputs:
433                         orig=os.path.join(dname,node.name)
434                         try:
435                                 shutil.copy2(orig,node.abspath())
436                                 os.utime(orig,None)
437                         except(OSError,IOError):
438                                 Logs.debug('task: failed retrieving file')
439                                 return None
440                 try:
441                         t2=os.stat(dname).st_mtime
442                 except OSError:
443                         return None
444                 if t1!=t2:
445                         return None
446                 for node in self.outputs:
447                         node.sig=sig
448                         if self.generator.bld.progress_bar<1:
449                                 self.generator.bld.to_log('restoring from cache %r\n'%node.abspath())
450                 self.cached=True
451                 return True
452         def put_files_cache(self):
453                 if getattr(self,'cached',None):
454                         return None
455                 if not getattr(self,'outputs',None):
456                         return None
457                 sig=self.signature()
458                 ssig=Utils.to_hex(self.uid())+Utils.to_hex(sig)
459                 dname=os.path.join(self.generator.bld.cache_global,ssig)
460                 tmpdir=tempfile.mkdtemp(prefix=self.generator.bld.cache_global+os.sep+'waf')
461                 try:
462                         shutil.rmtree(dname)
463                 except Exception:
464                         pass
465                 try:
466                         for node in self.outputs:
467                                 dest=os.path.join(tmpdir,node.name)
468                                 shutil.copy2(node.abspath(),dest)
469                 except(OSError,IOError):
470                         try:
471                                 shutil.rmtree(tmpdir)
472                         except Exception:
473                                 pass
474                 else:
475                         try:
476                                 os.rename(tmpdir,dname)
477                         except OSError:
478                                 try:
479                                         shutil.rmtree(tmpdir)
480                                 except Exception:
481                                         pass
482                         else:
483                                 try:
484                                         os.chmod(dname,Utils.O755)
485                                 except Exception:
486                                         pass
487 def is_before(t1,t2):
488         to_list=Utils.to_list
489         for k in to_list(t2.ext_in):
490                 if k in to_list(t1.ext_out):
491                         return 1
492         if t1.__class__.__name__ in to_list(t2.after):
493                 return 1
494         if t2.__class__.__name__ in to_list(t1.before):
495                 return 1
496         return 0
497 def set_file_constraints(tasks):
498         ins=Utils.defaultdict(set)
499         outs=Utils.defaultdict(set)
500         for x in tasks:
501                 for a in getattr(x,'inputs',[])+getattr(x,'dep_nodes',[]):
502                         ins[id(a)].add(x)
503                 for a in getattr(x,'outputs',[]):
504                         outs[id(a)].add(x)
505         links=set(ins.keys()).intersection(outs.keys())
506         for k in links:
507                 for a in ins[k]:
508                         a.run_after.update(outs[k])
509 def set_precedence_constraints(tasks):
510         cstr_groups=Utils.defaultdict(list)
511         for x in tasks:
512                 h=x.hash_constraints()
513                 cstr_groups[h].append(x)
514         keys=list(cstr_groups.keys())
515         maxi=len(keys)
516         for i in range(maxi):
517                 t1=cstr_groups[keys[i]][0]
518                 for j in range(i+1,maxi):
519                         t2=cstr_groups[keys[j]][0]
520                         if is_before(t1,t2):
521                                 a=i
522                                 b=j
523                         elif is_before(t2,t1):
524                                 a=j
525                                 b=i
526                         else:
527                                 continue
528                         aval=set(cstr_groups[keys[a]])
529                         for x in cstr_groups[keys[b]]:
530                                 x.run_after.update(aval)
531 def funex(c):
532         dc={}
533         exec(c,dc)
534         return dc['f']
535 reg_act=re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})",re.M)
536 def compile_fun_shell(line):
537         extr=[]
538         def repl(match):
539                 g=match.group
540                 if g('dollar'):return"$"
541                 elif g('backslash'):return'\\\\'
542                 elif g('subst'):extr.append((g('var'),g('code')));return"%s"
543                 return None
544         line=reg_act.sub(repl,line)or line
545         parm=[]
546         dvars=[]
547         app=parm.append
548         for(var,meth)in extr:
549                 if var=='SRC':
550                         if meth:app('tsk.inputs%s'%meth)
551                         else:app('" ".join([a.path_from(bld.bldnode) for a in tsk.inputs])')
552                 elif var=='TGT':
553                         if meth:app('tsk.outputs%s'%meth)
554                         else:app('" ".join([a.path_from(bld.bldnode) for a in tsk.outputs])')
555                 elif meth:
556                         if meth.startswith(':'):
557                                 m=meth[1:]
558                                 if m=='SRC':
559                                         m='[a.path_from(bld.bldnode) for a in tsk.inputs]'
560                                 elif m=='TGT':
561                                         m='[a.path_from(bld.bldnode) for a in tsk.outputs]'
562                                 elif m[:3]not in('tsk','gen','bld'):
563                                         dvars.extend([var,meth[1:]])
564                                         m='%r'%m
565                                 app('" ".join(tsk.colon(%r, %s))'%(var,m))
566                         else:
567                                 app('%s%s'%(var,meth))
568                 else:
569                         if not var in dvars:dvars.append(var)
570                         app("p('%s')"%var)
571         if parm:parm="%% (%s) "%(',\n\t\t'.join(parm))
572         else:parm=''
573         c=COMPILE_TEMPLATE_SHELL%(line,parm)
574         Logs.debug('action: %s'%c.strip().splitlines())
575         return(funex(c),dvars)
576 def compile_fun_noshell(line):
577         extr=[]
578         def repl(match):
579                 g=match.group
580                 if g('dollar'):return"$"
581                 elif g('subst'):extr.append((g('var'),g('code')));return"<<|@|>>"
582                 return None
583         line2=reg_act.sub(repl,line)
584         params=line2.split('<<|@|>>')
585         assert(extr)
586         buf=[]
587         dvars=[]
588         app=buf.append
589         for x in range(len(extr)):
590                 params[x]=params[x].strip()
591                 if params[x]:
592                         app("lst.extend(%r)"%params[x].split())
593                 (var,meth)=extr[x]
594                 if var=='SRC':
595                         if meth:app('lst.append(tsk.inputs%s)'%meth)
596                         else:app("lst.extend([a.path_from(bld.bldnode) for a in tsk.inputs])")
597                 elif var=='TGT':
598                         if meth:app('lst.append(tsk.outputs%s)'%meth)
599                         else:app("lst.extend([a.path_from(bld.bldnode) for a in tsk.outputs])")
600                 elif meth:
601                         if meth.startswith(':'):
602                                 m=meth[1:]
603                                 if m=='SRC':
604                                         m='[a.path_from(bld.bldnode) for a in tsk.inputs]'
605                                 elif m=='TGT':
606                                         m='[a.path_from(bld.bldnode) for a in tsk.outputs]'
607                                 elif m[:3]not in('tsk','gen','bld'):
608                                         dvars.extend([var,m])
609                                         m='%r'%m
610                                 app('lst.extend(tsk.colon(%r, %s))'%(var,m))
611                         else:
612                                 app('lst.extend(gen.to_list(%s%s))'%(var,meth))
613                 else:
614                         app('lst.extend(to_list(env[%r]))'%var)
615                         if not var in dvars:dvars.append(var)
616         if extr:
617                 if params[-1]:
618                         app("lst.extend(%r)"%params[-1].split())
619         fun=COMPILE_TEMPLATE_NOSHELL%"\n\t".join(buf)
620         Logs.debug('action: %s'%fun.strip().splitlines())
621         return(funex(fun),dvars)
622 def compile_fun(line,shell=False):
623         if line.find('<')>0 or line.find('>')>0 or line.find('&&')>0:
624                 shell=True
625         if shell:
626                 return compile_fun_shell(line)
627         else:
628                 return compile_fun_noshell(line)
629 def task_factory(name,func=None,vars=None,color='GREEN',ext_in=[],ext_out=[],before=[],after=[],shell=False,scan=None):
630         params={'vars':vars or[],'color':color,'name':name,'ext_in':Utils.to_list(ext_in),'ext_out':Utils.to_list(ext_out),'before':Utils.to_list(before),'after':Utils.to_list(after),'shell':shell,'scan':scan,}
631         if isinstance(func,str):
632                 params['run_str']=func
633         else:
634                 params['run']=func
635         cls=type(Task)(name,(Task,),params)
636         global classes
637         classes[name]=cls
638         return cls
639 def always_run(cls):
640         old=cls.runnable_status
641         def always(self):
642                 ret=old(self)
643                 if ret==SKIP_ME:
644                         ret=RUN_ME
645                 return ret
646         cls.runnable_status=always
647         return cls
648 def update_outputs(cls):
649         old_post_run=cls.post_run
650         def post_run(self):
651                 old_post_run(self)
652                 for node in self.outputs:
653                         node.sig=Utils.h_file(node.abspath())
654                         self.generator.bld.task_sigs[node.abspath()]=self.uid()
655         cls.post_run=post_run
656         old_runnable_status=cls.runnable_status
657         def runnable_status(self):
658                 status=old_runnable_status(self)
659                 if status!=RUN_ME:
660                         return status
661                 try:
662                         bld=self.generator.bld
663                         prev_sig=bld.task_sigs[self.uid()]
664                         if prev_sig==self.signature():
665                                 for x in self.outputs:
666                                         if not x.sig or bld.task_sigs[x.abspath()]!=self.uid():
667                                                 return RUN_ME
668                                 return SKIP_ME
669                 except KeyError:
670                         pass
671                 except IndexError:
672                         pass
673                 except AttributeError:
674                         pass
675                 return RUN_ME
676         cls.runnable_status=runnable_status
677         return cls