2
0
mirror of https://gitlab.com/ita1024/waf.git synced 2024-11-15 06:26:26 +01:00
waf/waflib/TaskGen.py

918 lines
26 KiB
Python
Raw Normal View History

2011-09-10 11:13:51 +02:00
#!/usr/bin/env python
# encoding: utf-8
2018-01-01 20:53:49 +01:00
# Thomas Nagy, 2005-2018 (ita)
2011-09-10 11:13:51 +02:00
"""
Task generators
The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code)
The instances can have various parameters, but the creation of task nodes (Task.py)
2016-06-25 16:23:06 +02:00
is deferred. To achieve this, various methods are called from the method "apply"
2011-09-10 11:13:51 +02:00
"""
2016-11-26 11:51:20 +01:00
import copy, re, os, functools
2012-04-02 01:42:35 +02:00
from waflib import Task, Utils, Logs, Errors, ConfigSet, Node
2011-09-10 11:13:51 +02:00
feats = Utils.defaultdict(set)
"""remember the methods declaring features"""
HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
2011-09-10 11:13:51 +02:00
class task_gen(object):
"""
Instances of this class create :py:class:`waflib.Task.Task` when
2011-09-10 11:13:51 +02:00
calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
A few notes:
* The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..)
* The 'features' are used to add methods to self.meths and then execute them
* The attribute 'path' is a node representing the location of the task generator
* The tasks created are added to the attribute *tasks*
* The attribute 'idx' is a counter of task generators in the same path
"""
mappings = Utils.ordered_iter_dict()
2016-06-25 16:23:06 +02:00
"""Mappings are global file extension mappings that are retrieved in the order of definition"""
2017-01-05 01:59:51 +01:00
prec = Utils.defaultdict(set)
2016-06-25 16:23:06 +02:00
"""Dict that holds the precedence execution rules for task generator methods"""
2011-09-10 11:13:51 +02:00
def __init__(self, *k, **kw):
"""
2016-06-25 16:23:06 +02:00
Task generator objects predefine various attributes (source, target) for possible
2011-09-10 11:13:51 +02:00
processing by process_rule (make-like rules) or process_source (extensions, misc methods)
2016-06-25 16:23:06 +02:00
Tasks are stored on the attribute 'tasks'. They are created by calling methods
listed in ``self.meths`` or referenced in the attribute ``features``
A topological sort is performed to execute the methods in correct order.
2011-09-10 11:13:51 +02:00
2016-06-25 16:23:06 +02:00
The extra key/value elements passed in ``kw`` are set as attributes
2011-09-10 11:13:51 +02:00
"""
2017-01-21 12:50:52 +01:00
self.source = []
2011-09-10 11:13:51 +02:00
self.target = ''
self.meths = []
"""
2016-06-25 16:23:06 +02:00
List of method names to execute (internal)
2011-09-10 11:13:51 +02:00
"""
self.features = []
"""
List of feature names for bringing new methods in
"""
self.tasks = []
"""
2016-06-25 16:23:06 +02:00
Tasks created are added to this list
2011-09-10 11:13:51 +02:00
"""
if not 'bld' in kw:
# task generators without a build context :-/
self.env = ConfigSet.ConfigSet()
self.idx = 0
self.path = None
else:
self.bld = kw['bld']
self.env = self.bld.env.derive()
self.path = self.bld.path # emulate chdir when reading scripts
# Provide a unique index per folder
# This is part of a measure to prevent output file name collisions
2017-08-13 22:37:31 +02:00
path = self.path.abspath()
2011-09-10 11:13:51 +02:00
try:
2017-08-13 22:37:31 +02:00
self.idx = self.bld.idx[path] = self.bld.idx.get(path, 0) + 1
2011-09-10 11:13:51 +02:00
except AttributeError:
self.bld.idx = {}
2017-08-13 22:37:31 +02:00
self.idx = self.bld.idx[path] = 1
2011-09-10 11:13:51 +02:00
# Record the global task generator count
try:
self.tg_idx_count = self.bld.tg_idx_count = self.bld.tg_idx_count + 1
except AttributeError:
self.tg_idx_count = self.bld.tg_idx_count = 1
2011-09-10 11:13:51 +02:00
for key, val in kw.items():
setattr(self, key, val)
def __str__(self):
2016-06-25 16:23:06 +02:00
"""Debugging helper"""
2011-09-10 11:13:51 +02:00
return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
def __repr__(self):
2016-06-25 16:23:06 +02:00
"""Debugging helper"""
2011-09-10 11:13:51 +02:00
lst = []
2016-04-22 21:10:22 +02:00
for x in self.__dict__:
if x not in ('env', 'bld', 'compiled_tasks', 'tasks'):
2011-09-10 11:13:51 +02:00
lst.append("%s=%s" % (x, repr(getattr(self, x))))
return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())
def get_cwd(self):
2016-06-25 16:23:06 +02:00
"""
Current working directory for the task generator, defaults to the build directory.
This is still used in a few places but it should disappear at some point as the classes
define their own working directory.
:rtype: :py:class:`waflib.Node.Node`
"""
return self.bld.bldnode
2011-09-10 11:13:51 +02:00
def get_name(self):
"""
2016-06-25 16:23:06 +02:00
If the attribute ``name`` is not set on the instance,
the name is computed from the target name::
2011-09-10 11:13:51 +02:00
def build(bld):
x = bld(name='foo')
x.get_name() # foo
y = bld(target='bar')
y.get_name() # bar
:rtype: string
:return: name of this task generator
"""
try:
return self._name
except AttributeError:
if isinstance(self.target, list):
lst = [str(x) for x in self.target]
name = self._name = ','.join(lst)
else:
name = self._name = str(self.target)
return name
def set_name(self, name):
self._name = name
name = property(get_name, set_name)
def to_list(self, val):
"""
2016-06-25 16:23:06 +02:00
Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list`
2011-09-10 11:13:51 +02:00
:type val: string or list of string
:param val: input to return as a list
:rtype: list
"""
2016-06-25 16:23:06 +02:00
if isinstance(val, str):
return val.split()
else:
return val
2011-09-10 11:13:51 +02:00
def post(self):
"""
2016-06-25 16:23:06 +02:00
Creates tasks for this task generators. The following operations are performed:
2011-09-10 11:13:51 +02:00
#. The body of this method is called only once and sets the attribute ``posted``
#. The attribute ``features`` is used to add more methods in ``self.meths``
#. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec`
#. The methods are then executed in order
#. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks`
"""
if getattr(self, 'posted', None):
return False
self.posted = True
keys = set(self.meths)
2016-11-03 22:42:17 +01:00
keys.update(feats['*'])
2011-09-10 11:13:51 +02:00
# add the methods listed in the features
self.features = Utils.to_list(self.features)
2016-11-03 22:42:17 +01:00
for x in self.features:
2011-09-10 11:13:51 +02:00
st = feats[x]
2016-11-03 22:42:17 +01:00
if st:
keys.update(st)
elif not x in Task.classes:
Logs.warn('feature %r does not exist - bind at least one method to it?', x)
2011-09-10 11:13:51 +02:00
# copy the precedence table
prec = {}
prec_tbl = self.prec
2011-09-10 11:13:51 +02:00
for x in prec_tbl:
if x in keys:
prec[x] = prec_tbl[x]
# elements disconnected
tmp = []
for a in keys:
for x in prec.values():
if a in x:
break
2011-09-10 11:13:51 +02:00
else:
tmp.append(a)
tmp.sort(reverse=True)
2011-09-10 11:13:51 +02:00
# topological sort
out = []
while tmp:
e = tmp.pop()
2016-11-03 22:42:17 +01:00
if e in keys:
out.append(e)
2011-09-10 11:13:51 +02:00
try:
nlst = prec[e]
except KeyError:
pass
else:
del prec[e]
for x in nlst:
for y in prec:
if x in prec[y]:
break
else:
tmp.append(x)
tmp.sort(reverse=True)
2011-09-10 11:13:51 +02:00
if prec:
buf = ['Cycle detected in the method execution:']
for k, v in prec.items():
buf.append('- %s after %s' % (k, [x for x in v if x in prec]))
raise Errors.WafError('\n'.join(buf))
2011-09-10 11:13:51 +02:00
self.meths = out
# then we run the methods in order
2016-03-19 14:46:22 +01:00
Logs.debug('task_gen: posting %s %d', self, id(self))
2011-09-10 11:13:51 +02:00
for x in out:
try:
v = getattr(self, x)
except AttributeError:
raise Errors.WafError('%r is not a valid task generator method' % x)
2016-03-19 14:46:22 +01:00
Logs.debug('task_gen: -> %s (%d)', x, id(self))
2011-09-10 11:13:51 +02:00
v()
2016-03-19 14:46:22 +01:00
Logs.debug('task_gen: posted %s', self.name)
2011-09-10 11:13:51 +02:00
return True
def get_hook(self, node):
"""
2016-06-25 16:23:06 +02:00
Returns the ``@extension`` method to call for a Node of a particular extension.
2011-09-10 11:13:51 +02:00
:param node: Input file to process
:type node: :py:class:`waflib.Tools.Node.Node`
:return: A method able to process the input node by looking at the extension
:rtype: function
"""
name = node.name
for k in self.mappings:
try:
if name.endswith(k):
return self.mappings[k]
except TypeError:
# regexps objects
if k.match(name):
return self.mappings[k]
keys = list(self.mappings.keys())
raise Errors.WafError("File %r has no mapping in %r (load a waf tool?)" % (node, keys))
2011-09-10 11:13:51 +02:00
def create_task(self, name, src=None, tgt=None, **kw):
2011-09-10 11:13:51 +02:00
"""
2016-06-25 16:23:06 +02:00
Creates task instances.
2011-09-10 11:13:51 +02:00
:param name: task class name
:type name: string
:param src: input nodes
:type src: list of :py:class:`waflib.Tools.Node.Node`
:param tgt: output nodes
:type tgt: list of :py:class:`waflib.Tools.Node.Node`
:return: A task object
:rtype: :py:class:`waflib.Task.Task`
2011-09-10 11:13:51 +02:00
"""
task = Task.classes[name](env=self.env.derive(), generator=self)
if src:
task.set_inputs(src)
if tgt:
task.set_outputs(tgt)
task.__dict__.update(kw)
2011-09-10 11:13:51 +02:00
self.tasks.append(task)
return task
def clone(self, env):
"""
2016-06-25 16:23:06 +02:00
Makes a copy of a task generator. Once the copy is made, it is necessary to ensure that the
2012-06-12 19:24:53 +02:00
it does not create the same output files as the original, or the same files may
be compiled several times.
2011-09-10 11:13:51 +02:00
:param env: A configuration set
:type env: :py:class:`waflib.ConfigSet.ConfigSet`
:return: A copy
:rtype: :py:class:`waflib.TaskGen.task_gen`
"""
newobj = self.bld()
for x in self.__dict__:
if x in ('env', 'bld'):
2011-09-10 11:13:51 +02:00
continue
elif x in ('path', 'features'):
2011-09-10 11:13:51 +02:00
setattr(newobj, x, getattr(self, x))
else:
setattr(newobj, x, copy.copy(getattr(self, x)))
newobj.posted = False
if isinstance(env, str):
newobj.env = self.bld.all_envs[env].derive()
else:
newobj.env = env.derive()
return newobj
def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
2011-09-10 11:13:51 +02:00
ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
"""
2016-06-25 16:23:06 +02:00
Creates a new mapping and a task class for processing files by extension.
2011-09-10 11:13:51 +02:00
See Tools/flex.py for an example.
:param name: name for the task class
:type name: string
:param rule: function to execute or string to be compiled in a function
:type rule: string or function
:param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
:type reentrant: int
2011-09-10 11:13:51 +02:00
:param color: color for the task output
:type color: string
:param ext_in: execute the task only after the files of such extensions are created
:type ext_in: list of string
:param ext_out: execute the task only before files of such extensions are processed
:type ext_out: list of string
:param before: execute instances of this task before classes of the given names
:type before: list of string
:param after: execute instances of this task after classes of the given names
:type after: list of string
2017-02-16 07:03:43 +01:00
:param decider: if present, function that returns a list of output file extensions (overrides ext_out for output files, but not for the build order)
2011-09-10 11:13:51 +02:00
:type decider: function
:param scan: scanner function for the task
:type scan: function
:param install_path: installation path for the output nodes
:type install_path: string
"""
ext_in = Utils.to_list(ext_in)
ext_out = Utils.to_list(ext_out)
if not name:
name = rule
cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)
def x_file(self, node):
if ext_in:
_ext_in = ext_in[0]
tsk = self.create_task(name, node)
cnt = 0
ext = decider(self, node) if decider else cls.ext_out
for x in ext:
k = node.change_ext(x, ext_in=_ext_in)
tsk.outputs.append(k)
if reentrant != None:
if cnt < int(reentrant):
self.source.append(k)
else:
# reinject downstream files into the build
for y in self.mappings: # ~ nfile * nextensions :-/
if k.name.endswith(y):
self.source.append(k)
break
cnt += 1
2011-09-10 11:13:51 +02:00
if install_path:
self.install_task = self.add_install_files(install_to=install_path, install_from=tsk.outputs)
2011-09-10 11:13:51 +02:00
return tsk
for x in cls.ext_in:
task_gen.mappings[x] = x_file
return x_file
def taskgen_method(func):
"""
2016-06-25 16:23:06 +02:00
Decorator that registers method as a task generator method.
2011-09-10 11:13:51 +02:00
The function must accept a task generator as first parameter::
from waflib.TaskGen import taskgen_method
@taskgen_method
def mymethod(self):
pass
:param func: task generator method to add
:type func: function
:rtype: function
"""
setattr(task_gen, func.__name__, func)
return func
def feature(*k):
"""
2016-06-25 16:23:06 +02:00
Decorator that registers a task generator method that will be executed when the
object attribute ``feature`` contains the corresponding key(s)::
2011-09-10 11:13:51 +02:00
from waflib.Task import feature
@feature('myfeature')
def myfunction(self):
print('that is my feature!')
def build(bld):
bld(features='myfeature')
:param k: feature names
:type k: list of string
"""
def deco(func):
setattr(task_gen, func.__name__, func)
for name in k:
feats[name].update([func.__name__])
return func
return deco
def before_method(*k):
"""
2016-06-25 16:23:06 +02:00
Decorator that registera task generator method which will be executed
2011-09-10 11:13:51 +02:00
before the functions of given name(s)::
from waflib.TaskGen import feature, before
@feature('myfeature')
@before_method('fun2')
def fun1(self):
print('feature 1!')
@feature('myfeature')
def fun2(self):
print('feature 2!')
def build(bld):
bld(features='myfeature')
:param k: method names
:type k: list of string
"""
def deco(func):
setattr(task_gen, func.__name__, func)
for fun_name in k:
task_gen.prec[func.__name__].add(fun_name)
2011-09-10 11:13:51 +02:00
return func
return deco
before = before_method
def after_method(*k):
"""
2016-06-25 16:23:06 +02:00
Decorator that registers a task generator method which will be executed
2011-09-10 11:13:51 +02:00
after the functions of given name(s)::
from waflib.TaskGen import feature, after
@feature('myfeature')
@after_method('fun2')
def fun1(self):
print('feature 1!')
@feature('myfeature')
def fun2(self):
print('feature 2!')
def build(bld):
bld(features='myfeature')
:param k: method names
:type k: list of string
"""
def deco(func):
setattr(task_gen, func.__name__, func)
for fun_name in k:
task_gen.prec[fun_name].add(func.__name__)
2011-09-10 11:13:51 +02:00
return func
return deco
after = after_method
def extension(*k):
"""
2016-06-25 16:23:06 +02:00
Decorator that registers a task generator method which will be invoked during
2011-09-10 11:13:51 +02:00
the processing of source files for the extension given::
from waflib import Task
class mytask(Task):
run_str = 'cp ${SRC} ${TGT}'
@extension('.moo')
def create_maa_file(self, node):
self.create_task('mytask', node, node.change_ext('.maa'))
def build(bld):
bld(source='foo.moo')
"""
def deco(func):
setattr(task_gen, func.__name__, func)
for x in k:
task_gen.mappings[x] = func
return func
return deco
@taskgen_method
def to_nodes(self, lst, path=None):
"""
2017-01-21 12:50:52 +01:00
Flatten the input list of string/nodes/lists into a list of nodes.
2011-09-10 11:13:51 +02:00
It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
:param lst: input list
:type lst: list of string and nodes
:param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
:type path: :py:class:`waflib.Tools.Node.Node`
:rtype: list of :py:class:`waflib.Tools.Node.Node`
"""
tmp = []
path = path or self.path
find = path.find_resource
if isinstance(lst, Node.Node):
2011-09-10 11:13:51 +02:00
lst = [lst]
for x in Utils.to_list(lst):
if isinstance(x, str):
node = find(x)
2017-01-21 12:50:52 +01:00
elif hasattr(x, 'name'):
2011-09-10 11:13:51 +02:00
node = x
2017-01-21 12:50:52 +01:00
else:
tmp.extend(self.to_nodes(x))
continue
2011-10-04 20:52:02 +02:00
if not node:
2017-01-21 12:50:52 +01:00
raise Errors.WafError('source not found: %r in %r' % (x, self))
2011-09-10 11:13:51 +02:00
tmp.append(node)
return tmp
@feature('*')
def process_source(self):
"""
2016-06-25 16:23:06 +02:00
Processes each element in the attribute ``source`` by extension.
2011-09-10 11:13:51 +02:00
#. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
#. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
#. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
#. When called, the methods may modify self.source to append more source to process
#. The mappings can map an extension or a filename (see the code below)
"""
self.source = self.to_nodes(getattr(self, 'source', []))
for node in self.source:
self.get_hook(node)(self, node)
@feature('*')
@before_method('process_source')
def process_rule(self):
"""
2016-06-25 16:23:06 +02:00
Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
2011-09-10 11:13:51 +02:00
def build(bld):
bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
Main attributes processed:
* rule: command to execute, it can be a tuple of strings for multiple commands
* chmod: permissions for the resulting files (integer value such as Utils.O755)
* shell: set to False to execute the command directly (default is True to use a shell)
* scan: scanner function
2018-07-29 01:46:58 +02:00
* vars: list of variables to trigger rebuilds, such as CFLAGS
* cls_str: string to display when executing the task
* cls_keyword: label to display when executing the task
* cache_rule: by default, try to re-use similar classes, set to False to disable
* source: list of Node or string objects representing the source files required by this task
* target: list of Node or string objects representing the files that this task creates
* cwd: current working directory (Node or string)
* stdout: standard output, set to None to prevent waf from capturing the text
* stderr: standard error, set to None to prevent waf from capturing the text
* timeout: timeout for command execution (Python 3)
* always: whether to always run the command (False by default)
* deep_inputs: whether the task must depend on the input file tasks too (False by default)
2011-09-10 11:13:51 +02:00
"""
if not getattr(self, 'rule', None):
return
# create the task class
2012-10-17 18:50:20 +02:00
name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
# or we can put the class in a cache for performance reasons
try:
cache = self.bld.cache_rule_attr
except AttributeError:
cache = self.bld.cache_rule_attr = {}
2016-12-22 18:14:36 +01:00
chmod = getattr(self, 'chmod', None)
shell = getattr(self, 'shell', True)
color = getattr(self, 'color', 'BLUE')
scan = getattr(self, 'scan', None)
_vars = getattr(self, 'vars', [])
cls_str = getattr(self, 'cls_str', None)
cls_keyword = getattr(self, 'cls_keyword', None)
use_cache = getattr(self, 'cache_rule', 'True')
deep_inputs = getattr(self, 'deep_inputs', False)
2016-12-22 18:14:36 +01:00
scan_val = has_deps = hasattr(self, 'deps')
if scan:
scan_val = id(scan)
key = Utils.h_list((name, self.rule, chmod, shell, color, cls_str, cls_keyword, scan_val, _vars, deep_inputs))
2016-12-22 18:14:36 +01:00
cls = None
2016-12-22 18:14:36 +01:00
if use_cache:
try:
2016-12-22 18:14:36 +01:00
cls = cache[key]
except KeyError:
pass
if not cls:
rule = self.rule
2016-12-22 18:14:36 +01:00
if chmod is not None:
def chmod_fun(tsk):
for x in tsk.outputs:
2016-12-22 18:14:36 +01:00
os.chmod(x.abspath(), tsk.generator.chmod)
if isinstance(rule, tuple):
rule = list(rule)
2016-12-22 12:07:08 +01:00
rule.append(chmod_fun)
rule = tuple(rule)
else:
rule = (rule, chmod_fun)
2016-12-22 18:14:36 +01:00
cls = Task.task_factory(name, rule, _vars, shell=shell, color=color)
if cls_str:
setattr(cls, '__str__', self.cls_str)
if cls_keyword:
setattr(cls, 'keyword', self.cls_keyword)
if deep_inputs:
Task.deep_inputs(cls)
2016-12-22 18:14:36 +01:00
if scan:
cls.scan = self.scan
2016-12-22 18:14:36 +01:00
elif has_deps:
def scan(self):
nodes = []
for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
node = self.generator.path.find_resource(x)
if not node:
self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
nodes.append(node)
return [nodes, []]
cls.scan = scan
2016-12-22 18:14:36 +01:00
if use_cache:
cache[key] = cls
2016-12-22 18:14:36 +01:00
# now create one instance
tsk = self.create_task(name)
2011-09-10 11:13:51 +02:00
2016-12-22 18:14:36 +01:00
for x in ('after', 'before', 'ext_in', 'ext_out'):
setattr(tsk, x, getattr(self, x, []))
if hasattr(self, 'stdout'):
tsk.stdout = self.stdout
if hasattr(self, 'stderr'):
tsk.stderr = self.stderr
2016-12-22 18:14:36 +01:00
if getattr(self, 'timeout', None):
tsk.timeout = self.timeout
2016-12-22 18:14:36 +01:00
if getattr(self, 'always', None):
tsk.always_run = True
2011-09-10 11:13:51 +02:00
if getattr(self, 'target', None):
if isinstance(self.target, str):
self.target = self.target.split()
if not isinstance(self.target, list):
self.target = [self.target]
for x in self.target:
if isinstance(x, str):
tsk.outputs.append(self.path.find_or_declare(x))
else:
x.parent.mkdir() # if a node was given, create the required folders
tsk.outputs.append(x)
if getattr(self, 'install_path', None):
self.install_task = self.add_install_files(install_to=self.install_path,
install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644))
2011-09-10 11:13:51 +02:00
if getattr(self, 'source', None):
tsk.inputs = self.to_nodes(self.source)
# bypass the execution of process_source by setting the source to an empty list
self.source = []
if getattr(self, 'cwd', None):
tsk.cwd = self.cwd
if isinstance(tsk.run, functools.partial):
# Python documentation says: "partial objects defined in classes
# behave like static methods and do not transform into bound
# methods during instance attribute look-up."
2016-11-26 11:51:20 +01:00
tsk.run = functools.partial(tsk.run, tsk)
2011-09-10 11:13:51 +02:00
@feature('seq')
def sequence_order(self):
"""
2016-06-25 16:23:06 +02:00
Adds a strict sequential constraint between the tasks generated by task generators.
2011-09-10 11:13:51 +02:00
It works because task generators are posted in order.
It will not post objects which belong to other folders.
Example::
bld(features='javac seq')
bld(features='jar seq')
To start a new sequence, set the attribute seq_start, for example::
obj = bld(features='seq')
obj.seq_start = True
Note that the method is executed in last position. This is more an
example than a widely-used solution.
"""
if self.meths and self.meths[-1] != 'sequence_order':
self.meths.append('sequence_order')
return
if getattr(self, 'seq_start', None):
return
# all the tasks previously declared must be run before these
if getattr(self.bld, 'prev', None):
self.bld.prev.post()
for x in self.bld.prev.tasks:
for y in self.tasks:
y.set_run_after(x)
self.bld.prev = self
re_m4 = re.compile('@(\w+)@', re.M)
class subst_pc(Task.Task):
"""
2016-06-25 16:23:06 +02:00
Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
2011-09-10 11:13:51 +02:00
in the substitution changes.
"""
def force_permissions(self):
"Private for the time being, we will probably refactor this into run_str=[run1,chmod]"
if getattr(self.generator, 'chmod', None):
for x in self.outputs:
os.chmod(x.abspath(), self.generator.chmod)
2011-09-10 11:13:51 +02:00
def run(self):
"Substitutes variables in a .in file"
2012-03-29 00:04:34 +02:00
if getattr(self.generator, 'is_copy', None):
for i, x in enumerate(self.outputs):
x.write(self.inputs[i].read('rb'), 'wb')
stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy
os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime))
self.force_permissions()
return None
2012-03-29 00:04:34 +02:00
if getattr(self.generator, 'fun', None):
ret = self.generator.fun(self)
if not ret:
self.force_permissions()
return ret
2011-09-10 11:13:51 +02:00
2017-01-21 13:28:06 +01:00
code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1'))
if getattr(self.generator, 'subst_fun', None):
code = self.generator.subst_fun(self, code)
2014-03-09 18:55:35 +01:00
if code is not None:
2017-01-21 13:28:06 +01:00
self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
self.force_permissions()
return None
2011-09-10 11:13:51 +02:00
# replace all % by %% to prevent errors by % signs
code = code.replace('%', '%%')
# extract the vars foo into lst and replace @foo@ by %(foo)s
lst = []
def repl(match):
g = match.group
if g(1):
lst.append(g(1))
return "%%(%s)s" % g(1)
return ''
code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
2011-09-10 11:13:51 +02:00
try:
d = self.generator.dct
except AttributeError:
d = {}
for x in lst:
tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
try:
tmp = ''.join(tmp)
except TypeError:
tmp = str(tmp)
d[x] = tmp
2011-09-10 11:13:51 +02:00
2012-05-08 21:58:44 +02:00
code = code % d
2017-01-21 13:28:06 +01:00
self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
self.generator.bld.raw_deps[self.uid()] = lst
2011-09-10 11:13:51 +02:00
# make sure the signature is updated
try:
delattr(self, 'cache_sig')
except AttributeError:
pass
2011-09-10 11:13:51 +02:00
self.force_permissions()
2011-09-10 11:13:51 +02:00
def sig_vars(self):
"""
Compute a hash (signature) of the variables used in the substitution
"""
bld = self.generator.bld
env = self.env
upd = self.m.update
if getattr(self.generator, 'fun', None):
upd(Utils.h_fun(self.generator.fun).encode())
2012-10-10 22:52:30 +02:00
if getattr(self.generator, 'subst_fun', None):
2012-11-11 22:58:50 +01:00
upd(Utils.h_fun(self.generator.subst_fun).encode())
2012-10-10 22:52:30 +02:00
2011-09-10 11:13:51 +02:00
# raw_deps: persistent custom values returned by the scanner
vars = self.generator.bld.raw_deps.get(self.uid(), [])
# hash both env vars and task generator attributes
act_sig = bld.hash_env_vars(env, vars)
upd(act_sig)
lst = [getattr(self.generator, x, '') for x in vars]
upd(Utils.h_list(lst))
return self.m.digest()
@extension('.pc.in')
def add_pcfile(self, node):
"""
2016-06-25 16:23:06 +02:00
Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
2011-09-10 11:13:51 +02:00
def build(bld):
bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
"""
tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
self.install_task = self.add_install_files(
install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs)
2011-09-10 11:13:51 +02:00
class subst(subst_pc):
pass
@feature('subst')
@before_method('process_source', 'process_rule')
def process_subst(self):
"""
2016-06-25 16:23:06 +02:00
Defines a transformation that substitutes the contents of *source* files to *target* files::
2011-09-10 11:13:51 +02:00
def build(bld):
bld(
features='subst',
source='foo.c.in',
target='foo.c',
install_path='${LIBDIR}/pkgconfig',
VAR = 'val'
)
The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
of the task generator object.
This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
"""
2012-03-29 00:04:34 +02:00
src = Utils.to_list(getattr(self, 'source', []))
if isinstance(src, Node.Node):
src = [src]
2012-03-29 00:04:34 +02:00
tgt = Utils.to_list(getattr(self, 'target', []))
if isinstance(tgt, Node.Node):
tgt = [tgt]
2011-09-10 11:13:51 +02:00
if len(src) != len(tgt):
2012-03-29 00:04:34 +02:00
raise Errors.WafError('invalid number of source/target for %r' % self)
2011-09-10 11:13:51 +02:00
for x, y in zip(src, tgt):
2012-03-29 00:04:34 +02:00
if not x or not y:
raise Errors.WafError('null source or target for %r' % self)
a, b = None, None
if isinstance(x, str) and isinstance(y, str) and x == y:
a = self.path.find_node(x)
b = self.path.get_bld().make_node(y)
if not os.path.isfile(b.abspath()):
b.parent.mkdir()
2012-03-29 00:04:34 +02:00
else:
if isinstance(x, str):
a = self.path.find_resource(x)
elif isinstance(x, Node.Node):
a = x
if isinstance(y, str):
b = self.path.find_or_declare(y)
elif isinstance(y, Node.Node):
b = y
if not a:
raise Errors.WafError('could not find %r for %r' % (x, self))
2012-03-29 00:04:34 +02:00
tsk = self.create_task('subst', a, b)
for k in ('after', 'before', 'ext_in', 'ext_out'):
val = getattr(self, k, None)
2011-09-10 11:13:51 +02:00
if val:
2012-03-29 00:04:34 +02:00
setattr(tsk, k, val)
2011-09-10 11:13:51 +02:00
# paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
for xt in HEADER_EXTS:
if b.name.endswith(xt):
tsk.ext_in = tsk.ext_in + ['.h']
break
inst_to = getattr(self, 'install_path', None)
if inst_to:
self.install_task = self.add_install_files(install_to=inst_to,
install_from=b, chmod=getattr(self, 'chmod', Utils.O644))
2011-09-10 11:13:51 +02:00
self.source = []