3 # Thomas Nagy, 2005-2008 (ita)
6 The class task_gen encapsulates the creation of task objects (low-level code)
7 The instances can have various parameters, but the creation of task nodes (Task.py)
8 is delayed. To achieve this, various methods are called from the method "apply"
10 The class task_gen contains lots of methods, and a configuration table:
11 * the methods to call (self.meths) can be specified dynamically (removing, adding, ..)
12 * the order of the methods (self.prec or by default task_gen.prec) is configurable
13 * new methods can be inserted dynamically without pasting old code
15 Additionally, task_gen provides the method apply_core
16 * file extensions are mapped to methods: def meth(self, name_or_node)
17 * if a mapping is not found in self.mappings, it is searched in task_gen.mappings
18 * when called, the functions may modify self.allnodes to re-add source to process
19 * the mappings can map an extension or a filename (see the code below)
21 WARNING: subclasses must reimplement the clone method
24 import os
, traceback
, copy
25 import Build
, Task
, Utils
, Logs
, Options
26 from Logs
import debug
, error
, warn
27 from Constants
import *
34 'importpath':'importpaths',
35 'install_var':'install_path',
36 'install_subdir':'install_path',
37 'inst_var':'install_path',
38 'inst_dir':'install_path',
42 class register_obj(type):
43 """no decorators for classes, so we use a metaclass
44 we store into task_gen.classes the classes that inherit task_gen
45 and whose names end in '_taskgen'
47 def __init__(cls
, name
, bases
, dict):
48 super(register_obj
, cls
).__init
__(name
, bases
, dict)
51 if name
.endswith(suffix
):
52 task_gen
.classes
[name
.replace(suffix
, '')] = cls
54 class task_gen(object):
56 Most methods are of the form 'def meth(self):' without any parameters
57 there are many of them, and they do many different things:
59 * task results installation
60 * environment modification
61 * attribute addition/removal
63 The inheritance approach is complicated
64 * mixing several languages at once
65 * subclassing is needed even for small changes
66 * inserting new methods is complicated
68 This new class uses a configuration table:
69 * adding new methods easily
70 * obtaining the order in which to call the methods
71 * postponing the method calls (post() -> apply)
73 Additionally, a 'traits' static attribute is provided:
74 * this list contains methods
75 * the methods can remove or add methods from self.meths
76 Example1: the attribute 'staticlib' is set on an instance
77 a method set in the list of traits is executed when the
78 instance is posted, it finds that flag and adds another method for execution
79 Example2: a method set in the list of traits finds the msvc
80 compiler (from self.env['MSVC']==1); more methods are added to self.meths
83 __metaclass__
= register_obj
86 prec
= Utils
.DefaultDict(list)
87 traits
= Utils
.DefaultDict(set)
90 def __init__(self
, *kw
, **kwargs
):
91 self
.prec
= Utils
.DefaultDict(list)
92 "map precedence of function names to call"
93 # so we will have to play with directed acyclic graphs
99 # list of methods to execute - does not touch it by hand unless you know
102 # list of mappings extension -> function
105 # list of features (see the documentation on traits)
106 self
.features
= list(kw
)
108 # not always a good idea
111 self
.default_chmod
= O644
112 self
.default_install_path
= None
114 # kind of private, beware of what you put in it, also, the contents are consumed
117 self
.bld
= kwargs
.get('bld', Build
.bld
)
118 self
.env
= self
.bld
.env
.copy()
120 self
.path
= self
.bld
.path
# emulate chdir when reading scripts
121 self
.name
= '' # give a name to the target (static+shlib with the same targetname ambiguity)
123 # provide a unique id
124 self
.idx
= self
.bld
.idx
[self
.path
.id] = self
.bld
.idx
.get(self
.path
.id, 0) + 1
126 for key
, val
in kwargs
.iteritems():
127 setattr(self
, key
, val
)
129 self
.bld
.task_manager
.add_task_gen(self
)
130 self
.bld
.all_task_gen
.append(self
)
133 return ("<task_gen '%s' of type %s defined in %s>"
134 % (self
.name
or self
.target
, self
.__class
__.__name
__, str(self
.path
)))
136 def __setattr__(self
, name
, attr
):
137 real
= typos
.get(name
, name
)
139 warn('typo %s -> %s' % (name
, real
))
141 traceback
.print_stack()
142 object.__setattr
__(self
, real
, attr
)
144 def to_list(self
, value
):
145 "helper: returns a list"
146 if isinstance(value
, str): return value
.split()
150 "order the methods to execute using self.prec or task_gen.prec"
151 keys
= set(self
.meths
)
153 # add the methods listed in the features
154 self
.features
= Utils
.to_list(self
.features
)
155 for x
in self
.features
+ ['*']:
156 st
= task_gen
.traits
[x
]
158 warn('feature %r does not exist - bind at least one method to it' % x
)
161 # copy the precedence table
163 prec_tbl
= self
.prec
or task_gen
.prec
166 prec
[x
] = prec_tbl
[x
]
168 # elements disconnected
171 for x
in prec
.values():
180 if e
in keys
: out
.append(e
)
194 if prec
: raise Utils
.WafError("graph has a cycle %s" % str(prec
))
198 # then we run the methods in order
199 debug('task_gen: posting %s %d', self
, id(self
))
203 except AttributeError:
204 raise Utils
.WafError("tried to retrieve %s which is not a valid method" % x
)
205 debug('task_gen: -> %s (%d)', x
, id(self
))
209 "runs the code to create the tasks, do not subclass"
211 if isinstance(self
.target
, list):
212 self
.name
= ' '.join(self
.target
)
214 self
.name
= self
.target
216 if getattr(self
, 'posted', None):
217 #error("OBJECT ALREADY POSTED" + str( self))
222 debug('task_gen: posted %s', self
.name
)
224 def get_hook(self
, ext
):
225 try: return self
.mappings
[ext
]
227 try: return task_gen
.mappings
[ext
]
228 except KeyError: return None
230 # TODO waf 1.6: always set the environment
231 # TODO waf 1.6: create_task(self, name, inputs, outputs)
232 def create_task(self
, name
, src
=None, tgt
=None, env
=None):
233 env
= env
or self
.env
234 task
= Task
.TaskBase
.classes
[name
](env
.copy(), generator
=self
)
238 task
.set_outputs(tgt
)
239 self
.tasks
.append(task
)
242 def name_to_obj(self
, name
):
243 return self
.bld
.name_to_obj(name
, self
.env
)
245 def get_tgen_by_name(self
, name
):
246 return self
.bld
.get_tgen_by_name(name
)
248 def find_sources_in_dirs(self
, dirnames
, excludes
=[], exts
=[]):
250 The attributes "excludes" and "exts" must be lists to avoid the confusion
251 find_sources_in_dirs('a', 'b', 'c') <-> find_sources_in_dirs('a b c')
253 do not use absolute paths
254 do not use paths outside of the source tree
255 the files or folder beginning by . are not returned
257 # TODO: remove in Waf 1.6
260 err_msg
= "'%s' attribute must be a list"
261 if not isinstance(excludes
, list):
262 raise Utils
.WscriptError(err_msg
% 'excludes')
263 if not isinstance(exts
, list):
264 raise Utils
.WscriptError(err_msg
% 'exts')
268 #make sure dirnames is a list helps with dirnames with spaces
269 dirnames
= self
.to_list(dirnames
)
271 ext_lst
= exts
or list(self
.mappings
.keys()) + list(task_gen
.mappings
.keys())
273 for name
in dirnames
:
274 anode
= self
.path
.find_dir(name
)
276 if not anode
or not anode
.is_child_of(self
.bld
.srcnode
):
277 raise Utils
.WscriptError("Unable to use '%s' - either because it's not a relative path" \
278 ", or it's not child of '%s'." % (name
, self
.bld
.srcnode
))
280 self
.bld
.rescan(anode
)
281 for name
in self
.bld
.cache_dir_contents
[anode
.id]:
283 # ignore hidden files
284 if name
.startswith('.'):
287 (base
, ext
) = os
.path
.splitext(name
)
288 if ext
in ext_lst
and not name
in lst
and not name
in excludes
:
289 lst
.append((anode
.relpath_gen(self
.path
) or '.') + os
.path
.sep
+ name
)
292 self
.source
= self
.to_list(self
.source
)
293 if not self
.source
: self
.source
= lst
294 else: self
.source
+= lst
296 def clone(self
, env
):
297 """when creating a clone in a task generator method,
298 make sure to set posted=False on the clone
299 else the other task generator will not create its tasks"""
300 newobj
= task_gen(bld
=self
.bld
)
301 for x
in self
.__dict
__:
302 if x
in ['env', 'bld']:
304 elif x
in ["path", "features"]:
305 setattr(newobj
, x
, getattr(self
, x
))
307 setattr(newobj
, x
, copy
.copy(getattr(self
, x
)))
309 newobj
.__class
__ = self
.__class
__
310 if isinstance(env
, str):
311 newobj
.env
= self
.bld
.all_envs
[env
].copy()
313 newobj
.env
= env
.copy()
317 def get_inst_path(self
):
318 return getattr(self
, '_install_path', getattr(self
, 'default_install_path', ''))
320 def set_inst_path(self
, val
):
321 self
._install
_path
= val
323 install_path
= property(get_inst_path
, set_inst_path
)
327 return getattr(self
, '_chmod', getattr(self
, 'default_chmod', O644
))
329 def set_chmod(self
, val
):
332 chmod
= property(get_chmod
, set_chmod
)
334 def declare_extension(var
, func
):
336 for x
in Utils
.to_list(var
):
337 task_gen
.mappings
[x
] = func
339 raise Utils
.WscriptError('declare_extension takes either a list or a string %r' % var
)
340 task_gen
.mapped
[func
.__name
__] = func
342 def declare_order(*k
):
348 if not f1
in task_gen
.prec
[f2
]:
349 task_gen
.prec
[f2
].append(f1
)
351 def declare_chain(name
='', action
='', ext_in
='', ext_out
='', reentrant
=True, color
='BLUE',
352 install
=0, before
=[], after
=[], decider
=None, rule
=None, scan
=None):
354 see Tools/flex.py for an example
355 while i do not like such wrappers, some people really do
358 action
= action
or rule
359 if isinstance(action
, str):
360 act
= Task
.simple_task_type(name
, action
, color
=color
)
362 act
= Task
.task_type_from_func(name
, action
, color
=color
)
363 act
.ext_in
= tuple(Utils
.to_list(ext_in
))
364 act
.ext_out
= tuple(Utils
.to_list(ext_out
))
365 act
.before
= Utils
.to_list(before
)
366 act
.after
= Utils
.to_list(after
)
369 def x_file(self
, node
):
371 ext
= decider(self
, node
)
375 if isinstance(ext
, str):
376 out_source
= node
.change_ext(ext
)
378 self
.allnodes
.append(out_source
)
379 elif isinstance(ext
, list):
380 out_source
= [node
.change_ext(x
) for x
in ext
]
382 for i
in xrange((reentrant
is True) and len(out_source
) or reentrant
):
383 self
.allnodes
.append(out_source
[i
])
385 # XXX: useless: it will fail on Utils.to_list above...
386 raise Utils
.WafError("do not know how to process %s" % str(ext
))
388 tsk
= self
.create_task(name
, node
, out_source
)
390 if node
.__class
__.bld
.is_install
:
391 tsk
.install
= install
393 declare_extension(act
.ext_in
, x_file
)
396 def bind_feature(name
, methods
):
397 lst
= Utils
.to_list(methods
)
398 task_gen
.traits
[name
].update(lst
)
401 All the following decorators are registration decorators, i.e add an attribute to current class
402 (task_gen and its derivatives), with same name as func, which points to func itself.
407 Now taskgen.sayHi() may be called
409 If python were really smart, it could infer itself the order of methods by looking at the
410 attributes. A prerequisite for execution is to have the attribute set before.
411 Intelligent compilers binding aspect-oriented programming and parallelization, what a nice topic for studies.
415 register a method as a task generator method
417 setattr(task_gen
, func
.__name
__, func
)
422 declare a task generator method that will be executed when the
423 object attribute 'feature' contains the corresponding key(s)
426 setattr(task_gen
, func
.__name
__, func
)
428 task_gen
.traits
[name
].update([func
.__name
__])
434 declare a task generator method which will be executed
435 before the functions of given name(s)
438 setattr(task_gen
, func
.__name
__, func
)
440 if not func
.__name
__ in task_gen
.prec
[fun_name
]:
441 task_gen
.prec
[fun_name
].append(func
.__name
__)
447 declare a task generator method which will be executed
448 after the functions of given name(s)
451 setattr(task_gen
, func
.__name
__, func
)
453 if not fun_name
in task_gen
.prec
[func
.__name
__]:
454 task_gen
.prec
[func
.__name
__].append(fun_name
)
460 declare a task generator method which will be invoked during
461 the processing of source files for the extension given
464 setattr(task_gen
, func
.__name
__, func
)
466 for x
in Utils
.to_list(var
):
467 task_gen
.mappings
[x
] = func
469 raise Utils
.WafError('extension takes either a list or a string %r' % var
)
470 task_gen
.mapped
[func
.__name
__] = func
474 # TODO make certain the decorators may be used here
476 def apply_core(self
):
477 """Process the attribute source
478 transform the names into file nodes
479 try to process the files by name first, later by extension"""
480 # get the list of folders to use by the scanners
481 # all our objects share the same include paths anyway
482 find_resource
= self
.path
.find_resource
484 for filename
in self
.to_list(self
.source
):
485 # if self.mappings or task_gen.mappings contains a file of the same name
486 x
= self
.get_hook(filename
)
490 node
= find_resource(filename
)
491 if not node
: raise Utils
.WafError("source not found: '%s' in '%s'" % (filename
, str(self
.path
)))
492 self
.allnodes
.append(node
)
494 for node
in self
.allnodes
:
495 # self.mappings or task_gen.mappings map the file extension to a function
496 x
= self
.get_hook(node
.suffix())
499 raise Utils
.WafError("Cannot guess how to process %s (got mappings %r in %r) -> try conf.check_tool(..)?" % \
500 (str(node
), self
.__class
__.mappings
.keys(), self
.__class
__))
502 feature('*')(apply_core
)
505 """Process the attribute rule, when provided the method apply_core will be disabled
507 if not getattr(self
, 'rule', None):
510 # someone may have removed it already
512 self
.meths
.remove('apply_core')
516 # get the function and the variables
520 if isinstance(func
, str):
521 # use the shell by default for user-defined commands
522 (func
, vars2
) = Task
.compile_fun('', self
.rule
, shell
=getattr(self
, 'shell', True))
523 func
.code
= self
.rule
525 # create the task class
526 name
= getattr(self
, 'name', None) or self
.target
or self
.rule
527 if not isinstance(name
, str):
529 cls
= Task
.task_type_from_func(name
, func
, getattr(self
, 'vars', vars2
))
530 cls
.color
= getattr(self
, 'color', 'BLUE')
532 # now create one instance
533 tsk
= self
.create_task(name
)
535 dep_vars
= getattr(self
, 'dep_vars', ['ruledeps'])
537 tsk
.dep_vars
= dep_vars
538 if isinstance(self
.rule
, str):
539 tsk
.env
.ruledeps
= self
.rule
541 # only works if the function is in a global module such as a waf tool
542 tsk
.env
.ruledeps
= Utils
.h_fun(self
.rule
)
544 # we assume that the user knows that without inputs or outputs
545 #if not getattr(self, 'target', None) and not getattr(self, 'source', None):
548 if getattr(self
, 'target', None):
550 tsk
.outputs
= [self
.path
.find_or_declare(x
) for x
in self
.to_list(self
.target
)]
552 if getattr(self
, 'source', None):
555 for x
in self
.to_list(self
.source
):
556 y
= self
.path
.find_resource(x
)
558 raise Utils
.WafError('input file %r could not be found (%r)' % (x
, self
.path
.abspath()))
562 tsk
.inputs
.extend(self
.allnodes
)
564 if getattr(self
, 'scan', None):
567 if getattr(self
, 'install_path', None):
568 tsk
.install_path
= self
.install_path
570 if getattr(self
, 'cwd', None):
573 if getattr(self
, 'on_results', None) or getattr(self
, 'update_outputs', None):
574 Task
.update_outputs(cls
)
576 if getattr(self
, 'always', None):
579 for x
in ['after', 'before', 'ext_in', 'ext_out']:
580 setattr(cls
, x
, getattr(self
, x
, []))
581 feature('*')(exec_rule
)
582 before('apply_core')(exec_rule
)
584 def sequence_order(self
):
586 add a strict sequential constraint between the tasks generated by task generators
587 it uses the fact that task generators are posted in order
588 it will not post objects which belong to other folders
589 there is also an awesome trick for executing the method in last position
592 bld(features='javac seq')
593 bld(features='jar seq')
595 to start a new sequence, set the attribute seq_start, for example:
598 if self
.meths
and self
.meths
[-1] != 'sequence_order':
599 self
.meths
.append('sequence_order')
602 if getattr(self
, 'seq_start', None):
605 # all the tasks previously declared must be run before these
606 if getattr(self
.bld
, 'prev', None):
608 for x
in self
.bld
.prev
.tasks
:
614 feature('seq')(sequence_order
)