This commit is contained in:
Thomas Nagy 2016-06-25 16:23:06 +02:00
parent ef6525c0bf
commit eaa83004c8
No known key found for this signature in database
GPG Key ID: 67A565EDFDF90E64
3 changed files with 259 additions and 156 deletions

View File

@ -70,14 +70,17 @@ def f(tsk):
'''
classes = {}
"Class tasks created by user scripts or Waf tools (maps names to class objects). Task classes defined in Waf tools are registered here through the metaclass :py:class:`waflib.Task.store_task_type`."
"""
The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
created by user scripts or Waf tools to this dict. It maps class names to class objects.
"""
class store_task_type(type):
"""
Metaclass: store the task classes into :py:const:`waflib.Task.classes`, or to the dict pointed
by the class attribute 'register'.
The attribute 'run_str' will be processed to compute a method 'run' on the task class
The decorator :py:func:`waflib.Task.cache_outputs` is also applied to the class
Metaclass: store the task classes into the dict pointed by the
class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
The attribute 'run_str' is compiled into a method 'run' bound to the task class.
"""
def __init__(cls, name, bases, dict):
super(store_task_type, cls).__init__(name, bases, dict)
@ -112,24 +115,22 @@ class TaskBase(evil):
'fun' in :py:meth:`waflib.Task.TaskBase.run`. When in doubt, create
subclasses of :py:class:`waflib.Task.Task` instead.
Subclasses should override these methods:
Subclasses must override these methods:
#. __str__: string to display to the user
#. runnable_status: ask the task if it should be run, skipped, or if we have to ask later
#. run: let threads execute the task
#. post_run: let threads update the data regarding the task (cache)
.. warning:: For backward compatibility reasons, the suffix "_task" is truncated in derived class names. This limitation will be removed in Waf 1.9.
#. run: what to do to execute the task
#. post_run: what to do after the task has been executed
"""
color = 'GREEN'
"""Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
ext_in = []
"""File extensions that objects of this task class might use"""
"""File extensions that objects of this task class may use"""
ext_out = []
"""File extensions that objects of this task class might create"""
"""File extensions that objects of this task class may create"""
before = []
"""List of task class names to execute before instances of this class"""
@ -144,7 +145,7 @@ class TaskBase(evil):
def __init__(self, *k, **kw):
"""
The base task class requires a task generator, which will be itself if missing
The base task class requires a task generator (set to *self* if missing)
"""
self.hasrun = NOT_RUN
try:
@ -153,21 +154,25 @@ class TaskBase(evil):
self.generator = self
def __repr__(self):
"for debugging purposes"
return '\n\t{task %r: %s %s}' % (self.__class__.__name__, id(self), str(getattr(self, 'fun', '')))
def __str__(self):
"string to display to the user"
"String to display to the user"
if hasattr(self, 'fun'):
return self.fun.__name__
return self.__class__.__name__
def keyword(self):
"Display keyword used to prettify the console outputs"
if hasattr(self, 'fun'):
return 'Function'
return 'Processing'
def get_cwd(self):
"""
:return: current working directory
:rtype: :py:class:`waflib.Node.Node`
"""
bld = self.generator.bld
ret = getattr(self, 'cwd', None) or getattr(self.generator.bld, 'cwd', bld.bldnode)
if isinstance(ret, str):
@ -175,6 +180,14 @@ class TaskBase(evil):
return ret
def quote_flag(self, x):
"""
Surround a process argument by quotes so that a list of arguments can be written to a file
:param x: flag
:type x: string
:return: quoted flag
:rtype: string
"""
old = x
if '\\' in x:
x = x.replace('\\', '\\\\')
@ -185,12 +198,23 @@ class TaskBase(evil):
return x
def split_argfile(self, cmd):
"""
Splits a list of process commands into the executable part and its list of arguments
:return: a tuple containing the executable first and then the rest of arguments
:rtype: tuple
"""
return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
def exec_command(self, cmd, **kw):
"""
Wrapper for :py:meth:`waflib.Context.Context.exec_command` which sets a current working directory to ``build.variant_dir``
Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
This version set the current working directory (``build.variant_dir``),
applies PATH settings (if self.env.PATH is provided), and can run long
commands through a temporary ``@argfile``.
:param cmd: process command to execute
:type cmd: list of string (best) or string (process will use a shell)
:return: the return code
:rtype: int
"""
@ -221,7 +245,7 @@ class TaskBase(evil):
def runnable_status(self):
"""
State of the task
Returns the Task status
:return: a task state in :py:const:`waflib.Task.RUN_ME`, :py:const:`waflib.Task.SKIP_ME` or :py:const:`waflib.Task.ASK_LATER`.
:rtype: int
@ -229,12 +253,20 @@ class TaskBase(evil):
return RUN_ME
def uid(self):
"""
Computes a unique identifier for the task
:rtype: string or bytes
"""
return Utils.SIG_NIL
def process(self):
"""
Assume that the task has had a ``master`` which is an instance of :py:class:`waflib.Runner.Parallel`.
Execute the task and then put it back in the queue :py:attr:`waflib.Runner.Parallel.out` (may be replaced by subclassing).
:return: 0 or None if everything is fine
:rtype: integer
"""
# remove the task signature immediately before it is executed
# in case of failure the task will be executed again
@ -274,7 +306,8 @@ class TaskBase(evil):
def run(self):
"""
Called by threads to execute the tasks. The default is empty and meant to be overridden in subclasses.
It is a bad idea to create nodes in this method (so, no node.ant_glob)
.. warning:: It is a bad idea to create nodes in this method, so avoid :py:meth:`waflib.Node.Node.ant_glob`
:rtype: int
"""
@ -283,11 +316,11 @@ class TaskBase(evil):
return 0
def post_run(self):
"Update the cache files (executed by threads). Override in subclasses."
"Update build data after successful Task execution. Override in subclasses."
pass
def log_display(self, bld):
"Write the execution status on the context logger"
"Writes the execution status on the context logger"
if self.generator.bld.progress_bar == 3:
return
@ -307,7 +340,7 @@ class TaskBase(evil):
def display(self):
"""
Return an execution status for the console, the progress bar, or the IDE output.
Returns an execution status for the console, the progress bar, or the IDE output.
:rtype: string
"""
@ -351,19 +384,18 @@ class TaskBase(evil):
def hash_constraints(self):
"""
Identify a task type for all the constraints relevant for the scheduler: precedence, file production
Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
:return: a hash value
:rtype: string
"""
cls = self.__class__
tup = (str(cls.before), str(cls.after), str(cls.ext_in), str(cls.ext_out), cls.__name__, cls.hcode)
h = hash(tup)
return h
return hash(tup)
def format_error(self):
"""
Error message to display to the user when a build fails
Returns an error message to display the build failure reasons
:rtype: string
"""
@ -385,7 +417,7 @@ class TaskBase(evil):
def colon(self, var1, var2):
"""
Support code for scriptlet expressions such as ${FOO_ST:FOO}
Enable scriptlet expressions of the form ${FOO_ST:FOO}
If the first variable (FOO_ST) is empty, then an empty list is returned
The results will be slightly different if FOO_ST is a list, for example::
@ -424,10 +456,10 @@ class Task(TaskBase):
nodes (if present).
"""
vars = []
"""Variables to depend on (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
"""ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
always_run = False
"""Specify whether task instances must always be executed or not"""
"""Specify whether task instances must always be executed or not (class attribute)"""
shell = False
"""Execute the command with the shell (class attribute)"""
@ -436,7 +468,7 @@ class Task(TaskBase):
TaskBase.__init__(self, *k, **kw)
self.env = kw['env']
"""ConfigSet object (make sure to provide one)"""
""":py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
self.inputs = []
"""List of input nodes, which represent the files used by the task instance"""
@ -470,6 +502,9 @@ class Task(TaskBase):
return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
def keyword(self):
"""
See :py:meth:`waflib.Task.TaskBase`
"""
name = self.__class__.__name__
if name.endswith(('lib', 'program')):
return 'Linking'
@ -494,10 +529,10 @@ class Task(TaskBase):
def uid(self):
"""
Return an identifier used to determine if tasks are up-to-date. Since the
Returns an identifier used to determine if tasks are up-to-date. Since the
identifier will be stored between executions, it must be:
- unique: no two tasks return the same value (for a given build context)
- unique for a task: no two tasks return the same value (for a given build context)
- the same for a given task instance
By default, the node paths, the class name, and the function are used
@ -521,7 +556,7 @@ class Task(TaskBase):
def set_inputs(self, inp):
"""
Append the nodes to the *inputs*
Appends the nodes to the *inputs* list
:param inp: input nodes
:type inp: node or list of nodes
@ -531,7 +566,7 @@ class Task(TaskBase):
def set_outputs(self, out):
"""
Append the nodes to the *outputs*
Appends the nodes to the *outputs* list
:param out: output nodes
:type out: node or list of nodes
@ -541,8 +576,7 @@ class Task(TaskBase):
def set_run_after(self, task):
"""
Run this task only after *task*. Affect :py:meth:`waflib.Task.runnable_status`
You probably want to use tsk.run_after.add(task) directly
Run this task only after the given *task*.
:param task: task
:type task: :py:class:`waflib.Task.Task`
@ -567,9 +601,14 @@ class Task(TaskBase):
sig = super(Task.Task, self).signature()
delattr(self, 'cache_sig')
return super(Task.Task, self).signature()
:return: the signature value
:rtype: string or bytes
"""
try: return self.cache_sig
except AttributeError: pass
try:
return self.cache_sig
except AttributeError:
pass
self.m = Utils.md5(self.hcode)
@ -591,8 +630,7 @@ class Task(TaskBase):
def runnable_status(self):
"""
Override :py:meth:`waflib.Task.TaskBase.runnable_status` to determine if the task is ready
to be run (:py:attr:`waflib.Task.Task.run_after`)
See :py:meth:`waflib.Task.TaskBase.runnable_status`
"""
#return 0 # benchmarking
@ -636,11 +674,8 @@ class Task(TaskBase):
def post_run(self):
"""
Called after successful execution to update the cache data :py:class:`waflib.Node.Node` sigs
and :py:attr:`waflib.Build.BuildContext.task_sigs`.
The node signature is obtained from the task signature, but the output nodes may also get the signature
of their contents. See the class decorator :py:func:`waflib.Task.update_outputs` if you need this behaviour.
Called after successful execution to record that the task has run by
updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
"""
bld = self.generator.bld
for node in self.outputs:
@ -657,7 +692,7 @@ class Task(TaskBase):
def sig_explicit_deps(self):
"""
Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.inputs`
Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
"""
bld = self.generator.bld
@ -685,7 +720,7 @@ class Task(TaskBase):
def sig_vars(self):
"""
Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.env` variables/values
Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
"""
sig = self.generator.bld.hash_env_vars(self.env, self.__class__.vars)
self.m.update(sig)
@ -702,24 +737,21 @@ class Task(TaskBase):
from waflib.Task import Task
class mytask(Task):
def scan(self, node):
return ((), ())
return ([], [])
The first and second lists are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
:py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
"""
def sig_implicit_deps(self):
"""
Used by :py:meth:`waflib.Task.Task.signature` hashes node signatures obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
The exception :py:class:`waflib.Errors.TaskRescan` is thrown
when a file has changed. When this occurs, :py:meth:`waflib.Task.Task.signature` is called
once again, and this method will be executed once again, this time calling :py:meth:`waflib.Task.Task.scan`
for searching the dependencies.
:rtype: hash value
when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
"""
bld = self.generator.bld
# get the task signatures from previous runs
@ -764,6 +796,9 @@ class Task(TaskBase):
"""
Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
:py:class:`waflib.Node.Node` returned by the scanner.
:return: a hash value for the implicit dependencies
:rtype: string or bytes
"""
upd = self.m.update
self.are_implicit_nodes_ready()
@ -777,9 +812,10 @@ class Task(TaskBase):
def are_implicit_nodes_ready(self):
"""
For each node returned by the scanner, see if there is a task behind it, and force the build order
For each node returned by the scanner, see if there is a task that creates it,
and infer the build order
Low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
"""
bld = self.generator.bld
try:
@ -823,7 +859,7 @@ if sys.hexversion > 0x3000000:
def is_before(t1, t2):
"""
Return a non-zero value if task t1 is to be executed before task t2::
Returns a non-zero value if task t1 is to be executed before task t2::
t1.ext_out = '.h'
t2.ext_in = '.h'
@ -831,9 +867,9 @@ def is_before(t1, t2):
t1.before = ['t2']
waflib.Task.is_before(t1, t2) # True
:param t1: task
:param t1: Task object
:type t1: :py:class:`waflib.Task.TaskBase`
:param t2: task
:param t2: Task object
:type t2: :py:class:`waflib.Task.TaskBase`
"""
to_list = Utils.to_list
@ -851,7 +887,7 @@ def is_before(t1, t2):
def set_file_constraints(tasks):
"""
Adds tasks to the task 'run_after' attribute based on the task inputs and outputs
Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
:param tasks: tasks
:type tasks: list of :py:class:`waflib.Task.TaskBase`
@ -871,7 +907,7 @@ def set_file_constraints(tasks):
def set_precedence_constraints(tasks):
"""
Add tasks to the task 'run_after' attribute based on the after/before/ext_out/ext_in attributes
Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
:param tasks: tasks
:type tasks: list of :py:class:`waflib.Task.TaskBase`
@ -906,7 +942,7 @@ def set_precedence_constraints(tasks):
def funex(c):
"""
Compile a function by 'exec'
Compiles a scriptlet expression into a Python function
:param c: function to compile
:type c: string
@ -922,16 +958,18 @@ re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
def compile_fun_shell(line):
"""
Create a compiled function to execute a process with the shell
WARNING: this method may disappear anytime, so use compile_fun instead
Creates a compiled function to execute a process through a sub-shell
"""
extr = []
def repl(match):
g = match.group
if g('dollar'): return "$"
elif g('backslash'): return '\\\\'
elif g('subst'): extr.append((g('var'), g('code'))); return "%s"
if g('dollar'):
return "$"
elif g('backslash'):
return '\\\\'
elif g('subst'):
extr.append((g('var'), g('code')))
return "%s"
return None
line = reg_act.sub(repl, line) or line
@ -994,8 +1032,7 @@ def compile_fun_shell(line):
reg_act_noshell = re.compile(r"(?P<space>\s+)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})|(?P<text>\S+)", re.M)
def compile_fun_noshell(line):
"""
Create a compiled function to execute a process without the shell
WARNING: this method may disappear anytime, so use compile_fun instead
Creates a compiled function to execute a process without a sub-shell
"""
buf = []
dvars = []
@ -1076,10 +1113,10 @@ def compile_fun_noshell(line):
def compile_fun(line, shell=False):
"""
Parse a string expression such as "${CC} ${SRC} -o ${TGT}" and return a pair containing:
Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
* the function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
* the list of variables that imply a dependency from self.env
* The function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
* The list of variables that must cause rebuilds when *env* data is modified
for example::
@ -1089,8 +1126,8 @@ def compile_fun(line, shell=False):
def build(bld):
bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
The env variables (CXX, ..) on the task must not hold dicts (order)
The reserved keywords *TGT* and *SRC* represent the task input and output nodes
The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
"""
if isinstance(line, str):
@ -1161,7 +1198,7 @@ def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[
def always_run(cls):
"""
Task class decorator, to be removed in waf 2.0
Deprecated Task class decorator (to be removed in waf 2.0)
Set all task instances of this class to be executed whenever a build is started
The task signature is calculated, but the result of the comparison between

View File

@ -7,9 +7,7 @@ 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)
is always postponed. To achieve this, various methods are called from the method "apply"
is deferred. To achieve this, various methods are called from the method "apply"
"""
import copy, re, os
@ -34,31 +32,28 @@ class task_gen(object):
"""
mappings = Utils.ordered_iter_dict()
"""Mappings are global file extension mappings, they are retrieved in the order of definition"""
"""Mappings are global file extension mappings that are retrieved in the order of definition"""
prec = Utils.defaultdict(list)
"""Dict holding the precedence rules for task generator methods"""
"""Dict that holds the precedence execution rules for task generator methods"""
def __init__(self, *k, **kw):
"""
The task generator objects predefine various attributes (source, target) for possible
Task generator objects predefine various attributes (source, target) for possible
processing by process_rule (make-like rules) or process_source (extensions, misc methods)
The 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 ease the method re-use.
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.
The extra key/value elements passed in kw are set as attributes
The extra key/value elements passed in ``kw`` are set as attributes
"""
# so we will have to play with directed acyclic graphs
# detect cycles, etc
self.source = ''
self.target = ''
self.meths = []
"""
List of method names to execute (it is usually a good idea to avoid touching this)
List of method names to execute (internal)
"""
self.features = []
@ -68,7 +63,7 @@ class task_gen(object):
self.tasks = []
"""
List of tasks created.
Tasks created are added to this list
"""
if not 'bld' in kw:
@ -92,11 +87,11 @@ class task_gen(object):
setattr(self, key, val)
def __str__(self):
"""for debugging purposes"""
"""Debugging helper"""
return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
def __repr__(self):
"""for debugging purposes"""
"""Debugging helper"""
lst = []
for x in self.__dict__:
if x not in ('env', 'bld', 'compiled_tasks', 'tasks'):
@ -104,11 +99,19 @@ class task_gen(object):
return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())
def get_cwd(self):
"""
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
def get_name(self):
"""
If not set, the name is computed from the target name::
If the attribute ``name`` is not set on the instance,
the name is computed from the target name::
def build(bld):
x = bld(name='foo')
@ -135,18 +138,20 @@ class task_gen(object):
def to_list(self, val):
"""
Ensure that a parameter is a list
Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list`
:type val: string or list of string
:param val: input to return as a list
:rtype: list
"""
if isinstance(val, str): return val.split()
else: return val
if isinstance(val, str):
return val.split()
else:
return val
def post(self):
"""
Create task objects. The following operations are performed:
Creates tasks for this task generators. The following operations are performed:
#. 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``
@ -226,6 +231,8 @@ class task_gen(object):
def get_hook(self, node):
"""
Returns the ``@extension`` method to call for a Node of a particular extension.
: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
@ -244,8 +251,7 @@ class task_gen(object):
def create_task(self, name, src=None, tgt=None, **kw):
"""
Wrapper for creating task instances. The classes are retrieved from the
context class if possible, then from the global dict Task.classes.
Creates task instances.
:param name: task class name
:type name: string
@ -267,7 +273,7 @@ class task_gen(object):
def clone(self, env):
"""
Make a copy of a task generator. Once the copy is made, it is necessary to ensure that the
Makes a copy of a task generator. Once the copy is made, it is necessary to ensure that the
it does not create the same output files as the original, or the same files may
be compiled several times.
@ -296,7 +302,7 @@ class task_gen(object):
def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
"""
Create a new mapping and a task class for processing files by extension.
Creates a new mapping and a task class for processing files by extension.
See Tools/flex.py for an example.
:param name: name for the task class
@ -361,7 +367,7 @@ def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
def taskgen_method(func):
"""
Decorator: register a method as a task generator method.
Decorator that registers method as a task generator method.
The function must accept a task generator as first parameter::
from waflib.TaskGen import taskgen_method
@ -378,8 +384,8 @@ def taskgen_method(func):
def feature(*k):
"""
Decorator: register a task generator method that will be executed when the
object attribute 'feature' contains the corresponding key(s)::
Decorator that registers a task generator method that will be executed when the
object attribute ``feature`` contains the corresponding key(s)::
from waflib.Task import feature
@feature('myfeature')
@ -400,7 +406,7 @@ def feature(*k):
def before_method(*k):
"""
Decorator: register a task generator method which will be executed
Decorator that registera task generator method which will be executed
before the functions of given name(s)::
from waflib.TaskGen import feature, before
@ -429,7 +435,7 @@ before = before_method
def after_method(*k):
"""
Decorator: register a task generator method which will be executed
Decorator that registers a task generator method which will be executed
after the functions of given name(s)::
from waflib.TaskGen import feature, after
@ -458,7 +464,7 @@ after = after_method
def extension(*k):
"""
Decorator: register a task generator method which will be invoked during
Decorator that registers a task generator method which will be invoked during
the processing of source files for the extension given::
from waflib import Task
@ -484,7 +490,7 @@ def extension(*k):
@taskgen_method
def to_nodes(self, lst, path=None):
"""
Convert the input list into a list of nodes.
Converts the input list into a list of nodes.
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`:
@ -515,7 +521,7 @@ def to_nodes(self, lst, path=None):
@feature('*')
def process_source(self):
"""
Process each element in the attribute ``source`` by extension.
Processes each element in the attribute ``source`` by extension.
#. 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`
@ -531,7 +537,7 @@ def process_source(self):
@before_method('process_source')
def process_rule(self):
"""
Process the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
def build(bld):
bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
@ -627,7 +633,7 @@ def process_rule(self):
@feature('seq')
def sequence_order(self):
"""
Add a strict sequential constraint between the tasks generated by task generators.
Adds a strict sequential constraint between the tasks generated by task generators.
It works because task generators are posted in order.
It will not post objects which belong to other folders.
@ -665,7 +671,7 @@ re_m4 = re.compile('@(\w+)@', re.M)
class subst_pc(Task.Task):
"""
Create *.pc* files from *.pc.in*. The task is executed whenever an input variable used
Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
in the substitution changes.
"""
@ -762,7 +768,7 @@ class subst_pc(Task.Task):
@extension('.pc.in')
def add_pcfile(self, node):
"""
Process *.pc.in* files to *.pc*. Install the results to ``${PREFIX}/lib/pkgconfig/``
Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
def build(bld):
bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
@ -778,7 +784,7 @@ class subst(subst_pc):
@before_method('process_source', 'process_rule')
def process_subst(self):
"""
Define a transformation that substitutes the contents of *source* files to *target* files::
Defines a transformation that substitutes the contents of *source* files to *target* files::
def build(bld):
bld(

View File

@ -82,7 +82,7 @@ rot_idx = 0
"Index of the current throbber character (progress bar)"
class ordered_iter_dict(dict):
"""An ordered dictionary that provides iteration from the most recently inserted keys first"""
"""Ordered dictionary that provides iteration from the most recently inserted keys first"""
def __init__(self, *k, **kw):
self.lst = deque()
dict.__init__(self, *k, **kw)
@ -106,6 +106,9 @@ class ordered_iter_dict(dict):
return reversed(self.lst)
class lru_node(object):
"""
Used by :py:class:`waflib.Utils.lru_cache`
"""
__slots__ = ('next', 'prev', 'key', 'val')
def __init__(self):
self.next = self
@ -118,7 +121,13 @@ class lru_cache(object):
__slots__ = ('maxlen', 'table', 'head')
def __init__(self, maxlen=100):
self.maxlen = maxlen
"""
Maximum amount of elements in the cache
"""
self.table = {}
"""
Mapping key-value
"""
self.head = lru_node()
for x in range(maxlen - 1):
node = lru_node()
@ -159,12 +168,13 @@ class lru_cache(object):
self.table[key] = node
is_win32 = os.sep == '\\' or sys.platform == 'win32' # msys2
"""
Whether this system is a Windows series
"""
def readf(fname, m='r', encoding='ISO8859-1'):
"""
Read an entire file into a string, use this function instead of os.open() whenever possible.
In practice the wrapper node.read(..) should be preferred to this function::
Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
def build(ctx):
from waflib import Utils
@ -202,9 +212,8 @@ def readf(fname, m='r', encoding='ISO8859-1'):
def writef(fname, data, m='w', encoding='ISO8859-1'):
"""
Write an entire file from a string, use this function instead of os.open() whenever possible.
In practice the wrapper node.write(..) should be preferred to this function::
Writes an entire file from a string.
See also :py:meth:`waflib.Node.Node.writef`::
def build(ctx):
from waflib import Utils
@ -237,6 +246,7 @@ def h_file(fname):
:type fname: string
:param fname: path to the file to hash
:return: hash of the file contents
:rtype: string or bytes
"""
f = open(fname, 'rb')
m = md5()
@ -344,8 +354,8 @@ Return the hexadecimal representation of a string
def listdir_win32(s):
"""
List the contents of a folder in a portable manner.
On Win32, return the list of drive letters: ['C:', 'X:', 'Z:']
Lists the contents of a folder in a portable manner.
On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
:type s: string
:param s: a string, which can be empty on Windows
@ -378,7 +388,7 @@ if is_win32:
def num2ver(ver):
"""
Convert a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
from waflib.Utils import num2ver
num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
@ -398,31 +408,29 @@ def num2ver(ver):
def ex_stack():
"""
Extract the stack to display exceptions
Deprecated: use traceback.format_exc()
Extracts the stack to display exceptions. Deprecated: use traceback.format_exc()
:return: a string represening the last exception
"""
# TODO remove in waf 2.0
return traceback.format_exc()
def to_list(sth):
def to_list(val):
"""
Convert a string argument to a list by splitting on spaces, and pass
through a list argument unchanged::
Converts a string argument to a list by splitting it by spaces.
Returns the object if not a string::
from waflib.Utils import to_list
lst = to_list("a b c d")
lst = to_list('a b c d')
:param sth: List or a string of items separated by spaces
:param val: list of string or space-separated string
:rtype: list
:return: Argument converted to list
"""
if isinstance(sth, str):
return sth.split()
if isinstance(val, str):
return val.split()
else:
return sth
return val
def split_path_unix(path):
return path.split('/')
@ -465,19 +473,20 @@ else:
split_path = split_path_unix
split_path.__doc__ = """
Split a path by / or \\. This function is not like os.path.split
Splits a path by / or \\; do not confuse this function with with ``os.path.split``
:type path: string
:param path: path to split
:return: list of strings
:return: list of string
"""
def check_dir(path):
"""
Ensure that a directory exists (similar to ``mkdir -p``).
Ensures that a directory exists (similar to ``mkdir -p``).
:type path: string
:param path: Path to directory
:raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
"""
if not os.path.isdir(path):
try:
@ -488,11 +497,14 @@ def check_dir(path):
def check_exe(name, env=None):
"""
Ensure that a program exists
Ensures that a program exists
:type name: string
:param name: name or path to program
:param name: path to the program
:param env: configuration object
:type env: :py:class:`waflib.ConfigSet.ConfigSet`
:return: path of the program or None
:raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
"""
if not name:
raise ValueError('Cannot execute an empty string!')
@ -504,7 +516,7 @@ def check_exe(name, env=None):
return os.path.abspath(name)
else:
env = env or os.environ
for path in env["PATH"].split(os.pathsep):
for path in env['PATH'].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, name)
if is_exe(exe_file):
@ -513,7 +525,7 @@ def check_exe(name, env=None):
def def_attrs(cls, **kw):
"""
Set default attributes on a class instance
Sets default attributes on a class instance
:type cls: class
:param cls: the class to update the given attributes in.
@ -526,7 +538,7 @@ def def_attrs(cls, **kw):
def quote_define_name(s):
"""
Convert a string to an identifier suitable for C defines.
Converts a string into an identifier suitable for C defines.
:type s: string
:param s: String to convert
@ -540,8 +552,8 @@ def quote_define_name(s):
def h_list(lst):
"""
Hash lists. For tuples, using hash(tup) is much more efficient,
except on python >= 3.3 where hash randomization assumes everybody is running a web application.
Hash lists. We would prefer to use hash(tup) for tuples because it is much more efficient,
but Python now enforces hash randomization by assuming everybody is running a web application.
:param lst: list to hash
:type lst: list of strings
@ -556,6 +568,7 @@ def h_fun(fun):
:param fun: function to hash
:type fun: function
:return: hash of the function
:rtype: string or bytes
"""
try:
return fun.code
@ -563,7 +576,7 @@ def h_fun(fun):
try:
h = inspect.getsource(fun)
except EnvironmentError:
h = "nocode"
h = 'nocode'
try:
fun.code = h
except AttributeError:
@ -572,8 +585,11 @@ def h_fun(fun):
def h_cmd(ins):
"""
Task command hashes are calculated by calling this function. The inputs can be
strings, functions, tuples/lists containing strings/functions
Hashes objects recursively
:param ins: input object
:type ins: string or list or tuple or function
:rtype: string or bytes
"""
# this function is not meant to be particularly fast
if isinstance(ins, str):
@ -592,7 +608,7 @@ def h_cmd(ins):
reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
def subst_vars(expr, params):
"""
Replace ${VAR} with the value of VAR taken from a dict or a config set::
Replaces ${VAR} with the value of VAR taken from a dict or a config set::
from waflib import Utils
s = Utils.subst_vars('${PREFIX}/bin', env)
@ -617,7 +633,8 @@ def subst_vars(expr, params):
def destos_to_binfmt(key):
"""
Return the binary format based on the unversioned platform name.
Returns the binary format based on the unversioned platform name,
and defaults to ``elf`` if nothing is found.
:param key: platform name
:type key: string
@ -631,7 +648,7 @@ def destos_to_binfmt(key):
def unversioned_sys_platform():
"""
Return the unversioned platform name.
Returns the unversioned platform name.
Some Python platform names contain versions, that depend on
the build environment, e.g. linux2, freebsd6, etc.
This returns the name without the version number. Exceptions are
@ -670,7 +687,7 @@ def unversioned_sys_platform():
def nada(*k, **kw):
"""
A function that does nothing
Does nothing
:return: None
"""
@ -706,7 +723,7 @@ class Timer(object):
def read_la_file(path):
"""
Read property files, used by msvc.py
Reads property files, used by msvc.py
:param path: file to read
:type path: string
@ -729,6 +746,8 @@ def run_once(fun):
def foo(k):
return 345*2343
.. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
:param fun: function to execute
:type fun: function
:return: the return value of the function executed
@ -746,6 +765,12 @@ def run_once(fun):
return wrap
def get_registry_app_path(key, filename):
"""
Returns the value of a registry key for an executable
:type key: string
:type filename: list of string
"""
if not winreg:
return None
try:
@ -757,6 +782,12 @@ def get_registry_app_path(key, filename):
return result
def lib64():
"""
Guess the default ``/usr/lib`` extension for 64-bit applications
:return: '64' or ''
:rtype: string
"""
# default settings for /usr/lib
if os.sep == '/':
if platform.architecture()[0] == '64bit':
@ -768,9 +799,17 @@ def sane_path(p):
# private function for the time being!
return os.path.abspath(os.path.expanduser(p))
process_pool = []
"""
List of processes started to execute sub-process commands
"""
def get_process():
"""
Returns a process object that can execute commands as sub-processes
:rtype: subprocess.Popen
"""
try:
return process_pool.pop()
except IndexError:
@ -779,6 +818,9 @@ def get_process():
return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
def run_prefork_process(cmd, kwargs, cargs):
"""
Delegates process execution to a pre-forked process instance.
"""
obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
proc = get_process()
@ -804,6 +846,9 @@ def run_prefork_process(cmd, kwargs, cargs):
return ret, out, err
def run_regular_process(cmd, kwargs, cargs={}):
"""
Executes a subprocess command by using subprocess.Popen
"""
proc = subprocess.Popen(cmd, **kwargs)
if kwargs.get('stdout') or kwargs.get('stderr'):
out, err = proc.communicate(**cargs)
@ -814,12 +859,27 @@ def run_regular_process(cmd, kwargs, cargs={}):
return status, out, err
def run_process(cmd, kwargs, cargs={}):
"""
Executes a subprocess by using a pre-forked process when possible
or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
and :py:func:`waflib.Utils.run_regular_process`
"""
if kwargs.get('stdout') and kwargs.get('stderr'):
return run_prefork_process(cmd, kwargs, cargs)
else:
return run_regular_process(cmd, kwargs, cargs)
def alloc_process_pool(n, force=False):
"""
Allocates an amount of processes to the default pool so its size is at least *n*.
It is useful to call this function early so that the pre-forked
processes use as little memory as possible.
:param n: pool size
:type n: integer
:param force: if True then *n* more processes are added to the existing pool
:type force: bool
"""
# mandatory on python2, unnecessary on python >= 3.2
global run_process, get_process, alloc_process_pool
if not force: