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 find_sources_in_dirs(self
, dirnames
, excludes
=[], exts
=[]):
247 The attributes "excludes" and "exts" must be lists to avoid the confusion
248 find_sources_in_dirs('a', 'b', 'c') <-> find_sources_in_dirs('a b c')
250 do not use absolute paths
251 do not use paths outside of the source tree
252 the files or folder beginning by . are not returned
254 # TODO: remove in Waf 1.6
257 err_msg
= "'%s' attribute must be a list"
258 if not isinstance(excludes
, list):
259 raise Utils
.WscriptError(err_msg
% 'excludes')
260 if not isinstance(exts
, list):
261 raise Utils
.WscriptError(err_msg
% 'exts')
265 #make sure dirnames is a list helps with dirnames with spaces
266 dirnames
= self
.to_list(dirnames
)
268 ext_lst
= exts
or list(self
.mappings
.keys()) + list(task_gen
.mappings
.keys())
270 for name
in dirnames
:
271 anode
= self
.path
.find_dir(name
)
273 if not anode
or not anode
.is_child_of(self
.bld
.srcnode
):
274 raise Utils
.WscriptError("Unable to use '%s' - either because it's not a relative path" \
275 ", or it's not child of '%s'." % (name
, self
.bld
.srcnode
))
277 self
.bld
.rescan(anode
)
278 for name
in self
.bld
.cache_dir_contents
[anode
.id]:
280 # ignore hidden files
281 if name
.startswith('.'):
284 (base
, ext
) = os
.path
.splitext(name
)
285 if ext
in ext_lst
and not name
in lst
and not name
in excludes
:
286 lst
.append((anode
.relpath_gen(self
.path
) or '.') + os
.path
.sep
+ name
)
289 self
.source
= self
.to_list(self
.source
)
290 if not self
.source
: self
.source
= lst
291 else: self
.source
+= lst
293 def clone(self
, env
):
294 """when creating a clone in a task generator method,
295 make sure to set posted=False on the clone
296 else the other task generator will not create its tasks"""
297 newobj
= task_gen(bld
=self
.bld
)
298 for x
in self
.__dict
__:
299 if x
in ['env', 'bld']:
301 elif x
in ["path", "features"]:
302 setattr(newobj
, x
, getattr(self
, x
))
304 setattr(newobj
, x
, copy
.copy(getattr(self
, x
)))
306 newobj
.__class
__ = self
.__class
__
307 if isinstance(env
, str):
308 newobj
.env
= self
.bld
.all_envs
[env
].copy()
310 newobj
.env
= env
.copy()
314 def get_inst_path(self
):
315 return getattr(self
, '_install_path', getattr(self
, 'default_install_path', ''))
317 def set_inst_path(self
, val
):
318 self
._install
_path
= val
320 install_path
= property(get_inst_path
, set_inst_path
)
324 return getattr(self
, '_chmod', getattr(self
, 'default_chmod', O644
))
326 def set_chmod(self
, val
):
329 chmod
= property(get_chmod
, set_chmod
)
331 def declare_extension(var
, func
):
333 for x
in Utils
.to_list(var
):
334 task_gen
.mappings
[x
] = func
336 raise Utils
.WscriptError('declare_extension takes either a list or a string %r' % var
)
337 task_gen
.mapped
[func
.__name
__] = func
339 def declare_order(*k
):
345 if not f1
in task_gen
.prec
[f2
]:
346 task_gen
.prec
[f2
].append(f1
)
348 def declare_chain(name
='', action
='', ext_in
='', ext_out
='', reentrant
=True, color
='BLUE',
349 install
=0, before
=[], after
=[], decider
=None, rule
=None, scan
=None):
351 see Tools/flex.py for an example
352 while i do not like such wrappers, some people really do
355 action
= action
or rule
356 if isinstance(action
, str):
357 act
= Task
.simple_task_type(name
, action
, color
=color
)
359 act
= Task
.task_type_from_func(name
, action
, color
=color
)
360 act
.ext_in
= tuple(Utils
.to_list(ext_in
))
361 act
.ext_out
= tuple(Utils
.to_list(ext_out
))
362 act
.before
= Utils
.to_list(before
)
363 act
.after
= Utils
.to_list(after
)
366 def x_file(self
, node
):
368 ext
= decider(self
, node
)
372 if isinstance(ext
, str):
373 out_source
= node
.change_ext(ext
)
375 self
.allnodes
.append(out_source
)
376 elif isinstance(ext
, list):
377 out_source
= [node
.change_ext(x
) for x
in ext
]
379 for i
in xrange((reentrant
is True) and len(out_source
) or reentrant
):
380 self
.allnodes
.append(out_source
[i
])
382 # XXX: useless: it will fail on Utils.to_list above...
383 raise Utils
.WafError("do not know how to process %s" % str(ext
))
385 tsk
= self
.create_task(name
, node
, out_source
)
387 if node
.__class
__.bld
.is_install
:
388 tsk
.install
= install
390 declare_extension(act
.ext_in
, x_file
)
393 def bind_feature(name
, methods
):
394 lst
= Utils
.to_list(methods
)
395 task_gen
.traits
[name
].update(lst
)
398 All the following decorators are registration decorators, i.e add an attribute to current class
399 (task_gen and its derivatives), with same name as func, which points to func itself.
404 Now taskgen.sayHi() may be called
406 If python were really smart, it could infer itself the order of methods by looking at the
407 attributes. A prerequisite for execution is to have the attribute set before.
408 Intelligent compilers binding aspect-oriented programming and parallelization, what a nice topic for studies.
412 register a method as a task generator method
414 setattr(task_gen
, func
.__name
__, func
)
419 declare a task generator method that will be executed when the
420 object attribute 'feature' contains the corresponding key(s)
423 setattr(task_gen
, func
.__name
__, func
)
425 task_gen
.traits
[name
].update([func
.__name
__])
431 declare a task generator method which will be executed
432 before the functions of given name(s)
435 setattr(task_gen
, func
.__name
__, func
)
437 if not func
.__name
__ in task_gen
.prec
[fun_name
]:
438 task_gen
.prec
[fun_name
].append(func
.__name
__)
444 declare a task generator method which will be executed
445 after the functions of given name(s)
448 setattr(task_gen
, func
.__name
__, func
)
450 if not fun_name
in task_gen
.prec
[func
.__name
__]:
451 task_gen
.prec
[func
.__name
__].append(fun_name
)
457 declare a task generator method which will be invoked during
458 the processing of source files for the extension given
461 setattr(task_gen
, func
.__name
__, func
)
463 for x
in Utils
.to_list(var
):
464 task_gen
.mappings
[x
] = func
466 raise Utils
.WafError('extension takes either a list or a string %r' % var
)
467 task_gen
.mapped
[func
.__name
__] = func
471 # TODO make certain the decorators may be used here
473 def apply_core(self
):
474 """Process the attribute source
475 transform the names into file nodes
476 try to process the files by name first, later by extension"""
477 # get the list of folders to use by the scanners
478 # all our objects share the same include paths anyway
479 find_resource
= self
.path
.find_resource
481 for filename
in self
.to_list(self
.source
):
482 # if self.mappings or task_gen.mappings contains a file of the same name
483 x
= self
.get_hook(filename
)
487 node
= find_resource(filename
)
488 if not node
: raise Utils
.WafError("source not found: '%s' in '%s'" % (filename
, str(self
.path
)))
489 self
.allnodes
.append(node
)
491 for node
in self
.allnodes
:
492 # self.mappings or task_gen.mappings map the file extension to a function
493 x
= self
.get_hook(node
.suffix())
496 raise Utils
.WafError("Cannot guess how to process %s (got mappings %r in %r) -> try conf.check_tool(..)?" % \
497 (str(node
), self
.__class
__.mappings
.keys(), self
.__class
__))
499 feature('*')(apply_core
)
502 """Process the attribute rule, when provided the method apply_core will be disabled
504 if not getattr(self
, 'rule', None):
507 # someone may have removed it already
509 self
.meths
.remove('apply_core')
513 # get the function and the variables
517 if isinstance(func
, str):
518 # use the shell by default for user-defined commands
519 (func
, vars2
) = Task
.compile_fun('', self
.rule
, shell
=getattr(self
, 'shell', True))
520 func
.code
= self
.rule
522 # create the task class
523 name
= getattr(self
, 'name', None) or self
.target
or self
.rule
524 if not isinstance(name
, str):
526 cls
= Task
.task_type_from_func(name
, func
, getattr(self
, 'vars', vars2
))
527 cls
.color
= getattr(self
, 'color', 'BLUE')
529 # now create one instance
530 tsk
= self
.create_task(name
)
532 dep_vars
= getattr(self
, 'dep_vars', ['ruledeps'])
534 tsk
.dep_vars
= dep_vars
535 if isinstance(self
.rule
, str):
536 tsk
.env
.ruledeps
= self
.rule
538 # only works if the function is in a global module such as a waf tool
539 tsk
.env
.ruledeps
= Utils
.h_fun(self
.rule
)
541 # we assume that the user knows that without inputs or outputs
542 #if not getattr(self, 'target', None) and not getattr(self, 'source', None):
545 if getattr(self
, 'target', None):
547 tsk
.outputs
= [self
.path
.find_or_declare(x
) for x
in self
.to_list(self
.target
)]
549 if getattr(self
, 'source', None):
552 for x
in self
.to_list(self
.source
):
553 y
= self
.path
.find_resource(x
)
555 raise Utils
.WafError('input file %r could not be found (%r)' % (x
, self
.path
.abspath()))
559 tsk
.inputs
.extend(self
.allnodes
)
561 if getattr(self
, 'scan', None):
564 if getattr(self
, 'install_path', None):
565 tsk
.install_path
= self
.install_path
567 if getattr(self
, 'cwd', None):
570 if getattr(self
, 'on_results', None):
571 Task
.update_outputs(cls
)
573 if getattr(self
, 'always', None):
576 for x
in ['after', 'before', 'ext_in', 'ext_out']:
577 setattr(cls
, x
, getattr(self
, x
, []))
578 feature('*')(exec_rule
)
579 before('apply_core')(exec_rule
)
581 def sequence_order(self
):
583 add a strict sequential constraint between the tasks generated by task generators
584 it uses the fact that task generators are posted in order
585 it will not post objects which belong to other folders
586 there is also an awesome trick for executing the method in last position
589 bld(features='javac seq')
590 bld(features='jar seq')
592 to start a new sequence, set the attribute seq_start, for example:
595 if self
.meths
and self
.meths
[-1] != 'sequence_order':
596 self
.meths
.append('sequence_order')
599 if getattr(self
, 'seq_start', None):
602 # all the tasks previously declared must be run before these
603 if getattr(self
.bld
, 'prev', None):
605 for x
in self
.bld
.prev
.tasks
:
611 feature('seq')(sequence_order
)