3
# Thomas Nagy, 2010 (ita)
6
Classes and functions required for waf commands
10
from waflib import Utils, Errors, Logs
13
# the following 3 constants are updated on each new release (do not touch)
15
"""Constant updated on new releases"""
18
"""Constant updated on new releases"""
20
WAFREVISION="a7e69d6b81b04729804754c4d5214da063779a65"
21
"""Constant updated on new releases"""
24
"""Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
26
DBFILE = '.wafpickle-%d' % ABI
27
"""Name of the pickle file for storing the build data"""
30
"""Default application name (used by ``waf dist``)"""
33
"""Default application version (used by ``waf dist``)"""
36
"""The variable name for the top-level directory in wscript files"""
39
"""The variable name for the output directory in wscript files"""
41
WSCRIPT_FILE = 'wscript'
42
"""Name of the waf script files"""
46
"""Directory from which waf has been called"""
48
"""Location of the wscript file to use as the entry point"""
50
"""Location of the project directory (top), if the project was configured"""
52
"""Location of the build directory (out), if the project was configured"""
54
"""Directory containing the waf modules"""
57
"""Local repository containing additional Waf tools (plugins)"""
58
remote_repo = 'http://waf.googlecode.com/git/'
60
Remote directory containing downloadable waf tools. The missing tools can be downloaded by using::
62
$ waf configure --download
65
remote_locs = ['waflib/extras', 'waflib/Tools']
67
Remote directories for use with :py:const:`waflib.Context.remote_repo`
72
Module representing the main wscript file (see :py:const:`waflib.Context.run_dir`)
81
List of :py:class:`waflib.Context.Context` subclasses that can be used as waf commands. The classes
82
are added automatically by a metaclass.
86
def create_context(cmd_name, *k, **kw):
88
Create a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
89
Used in particular by :py:func:`waflib.Scripting.run_command`
91
:param cmd_name: command
92
:type cmd_name: string
93
:param k: arguments to give to the context class initializer
95
:param k: keyword arguments to give to the context class initializer
100
if x.cmd == cmd_name:
102
ctx = Context(*k, **kw)
106
class store_context(type):
108
Metaclass for storing the command classes into the list :py:const:`waflib.Context.classes`
109
Context classes must provide an attribute 'cmd' representing the command to execute
111
def __init__(cls, name, bases, dict):
112
super(store_context, cls).__init__(name, bases, dict)
115
if name == 'ctx' or name == 'Context':
120
except AttributeError:
121
raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
123
if not getattr(cls, 'fun', None):
127
classes.insert(0, cls)
129
ctx = store_context('ctx', (object,), {})
130
"""Base class for the :py:class:`waflib.Context.Context` classes"""
134
Default context for waf commands, and base class for new command contexts.
136
Context objects are passed to top-level functions::
139
print(ctx.__class__.__name__) # waflib.Context.Context
141
Subclasses must define the attribute 'cmd':
143
:param cmd: command to execute as in ``waf cmd``
145
:param fun: function name to execute when the command is called
148
.. inheritance-diagram:: waflib.Context.Context waflib.Build.BuildContext waflib.Build.InstallContext waflib.Build.UninstallContext waflib.Build.StepContext waflib.Build.ListContext waflib.Configure.ConfigurationContext waflib.Scripting.Dist waflib.Scripting.DistCheck waflib.Build.CleanContext
154
Shortcut to :py:mod:`waflib.Errors` provided for convenience
159
A cache for modules (wscript files) read by :py:meth:`Context.Context.load`
162
def __init__(self, **kw):
169
# binds the context to the nodes in use to avoid a context singleton
170
class node_class(waflib.Node.Node):
172
self.node_class = node_class
173
self.node_class.__module__ = "waflib.Node"
174
self.node_class.__name__ = "Nod3"
175
self.node_class.ctx = self
177
self.root = self.node_class('', None)
178
self.cur_script = None
179
self.path = self.root.find_dir(rd)
182
self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
187
Return a hash value for storing context objects in dicts or sets. The value is not persistent.
194
def load(self, tool_list, *k, **kw):
196
Load a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun` from it.
197
A ``tooldir`` value may be provided as a list of module paths.
199
:type tool_list: list of string or space-separated string
200
:param tool_list: list of Waf tools to use
202
tools = Utils.to_list(tool_list)
203
path = Utils.to_list(kw.get('tooldir', ''))
206
module = load_tool(t, path)
207
fun = getattr(module, kw.get('name', self.fun), None)
213
Execute the command. Redefine this method in subclasses.
216
self.recurse([os.path.dirname(g_module.root_path)])
218
def pre_recurse(self, node):
220
Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`. The node given is set
221
as an attribute ``self.cur_script``, and as the current path ``self.path``
224
:type node: :py:class:`waflib.Node.Node`
226
self.stack_path.append(self.cur_script)
228
self.cur_script = node
229
self.path = node.parent
231
def post_recurse(self, node):
233
Restore ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
236
:type node: :py:class:`waflib.Node.Node`
238
self.cur_script = self.stack_path.pop()
240
self.path = self.cur_script.parent
242
def recurse(self, dirs, name=None, mandatory=True, once=True):
244
Run user code from the supplied list of directories.
245
The directories can be either absolute, or relative to the directory
246
of the wscript file. The methods :py:meth:`waflib.Context.Context.pre_recurse` and :py:meth:`waflib.Context.Context.post_recurse`
247
are called immediately before and after a script has been executed.
249
:param dirs: List of directories to visit
250
:type dirs: list of string or space-separated string
251
:param name: Name of function to invoke from the wscript
253
:param mandatory: whether sub wscript files are required to exist
254
:type mandatory: bool
255
:param once: read the script file once for a particular context
259
cache = self.recurse_cache
261
cache = self.recurse_cache = {}
263
for d in Utils.to_list(dirs):
265
if not os.path.isabs(d):
266
# absolute paths only
267
d = os.path.join(self.path.abspath(), d)
269
WSCRIPT = os.path.join(d, WSCRIPT_FILE)
270
WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
272
node = self.root.find_node(WSCRIPT_FUN)
273
if node and (not once or node not in cache):
275
self.pre_recurse(node)
277
function_code = node.read('rU')
278
exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
280
self.post_recurse(node)
282
node = self.root.find_node(WSCRIPT)
283
tup = (node, name or self.fun)
284
if node and (not once or tup not in cache):
286
self.pre_recurse(node)
288
wscript_module = load_module(node.abspath())
289
user_function = getattr(wscript_module, (name or self.fun), None)
290
if not user_function:
293
raise Errors.WafError('No function %s defined in %s' % (name or self.fun, node.abspath()))
296
self.post_recurse(node)
300
raise Errors.WafError('No wscript file in directory %s' % d)
302
def exec_command(self, cmd, **kw):
304
Execute a command and return the exit status. If the context has the attribute 'log',
305
capture and log the process stderr/stdout for logging purposes::
308
ret = tsk.generator.bld.exec_command('touch foo.txt')
311
Do not confuse this method with :py:meth:`waflib.Context.Context.cmd_and_log` which is used to
312
return the standard output/error values.
314
:param cmd: command argument for subprocess.Popen
315
:param kw: keyword arguments for subprocess.Popen
317
subprocess = Utils.subprocess
318
kw['shell'] = isinstance(cmd, str)
319
Logs.debug('runner: %r' % cmd)
320
Logs.debug('runner_env: kw=%s' % kw)
324
# warning: may deadlock with a lot of output (subprocess limitation)
326
self.logger.info(cmd)
328
kw['stdout'] = kw['stderr'] = subprocess.PIPE
329
p = subprocess.Popen(cmd, **kw)
330
(out, err) = p.communicate()
332
self.logger.debug('out: %s' % out.decode(sys.stdout.encoding or 'iso8859-1'))
334
self.logger.error('err: %s' % err.decode(sys.stdout.encoding or 'iso8859-1'))
337
p = subprocess.Popen(cmd, **kw)
342
def cmd_and_log(self, cmd, **kw):
344
Execute a command and return stdout if the execution is successful.
345
An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
346
will be bound to the WafError object::
349
out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
350
(out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
352
conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
353
except Exception as e:
354
print(e.stdout, e.stderr)
356
:param cmd: args for subprocess.Popen
357
:param kw: keyword arguments for subprocess.Popen
359
subprocess = Utils.subprocess
360
kw['shell'] = isinstance(cmd, str)
361
Logs.debug('runner: %r' % cmd)
370
to_ret = kw['output']
375
kw['stdout'] = kw['stderr'] = subprocess.PIPE
379
p = subprocess.Popen(cmd, **kw)
380
(out, err) = p.communicate()
381
except Exception as e:
382
raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
384
if not isinstance(out, str):
385
out = out.decode(sys.stdout.encoding or 'iso8859-1')
386
if not isinstance(err, str):
387
err = err.decode(sys.stdout.encoding or 'iso8859-1')
389
if out and quiet != STDOUT and quiet != BOTH:
390
self.to_log('out: %s' % out)
391
if err and quiet != STDERR and quiet != BOTH:
392
self.to_log('err: %s' % err)
395
e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode))
396
e.returncode = p.returncode
403
elif to_ret == STDERR:
407
def fatal(self, msg, ex=None):
409
Raise a configuration error to interrupt the execution immediately::
412
conf.fatal('a requirement is missing')
414
:param msg: message to display
416
:param ex: optional exception object
420
self.logger.info('from %s: %s' % (self.path.abspath(), msg))
422
msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
425
raise self.errors.ConfigurationError(msg, ex=ex)
427
def to_log(self, msg):
429
Log some information to the logger (if present), or to stderr. If the message is empty,
433
bld.to_log('starting the build')
435
When in doubt, override this method, or provide a logger on the context class.
443
self.logger.info(msg)
445
sys.stderr.write(str(msg))
449
def msg(self, msg, result, color=None):
451
Print a configuration message of the form ``msg: result``.
452
The second part of the message will be in colors. The output
453
can be disabled easly by setting ``in_msg`` to a positive value::
457
conf.msg('Checking for library foo', 'ok')
460
:param msg: message to display to the user
462
:param result: result to display
463
:type result: string or boolean
464
:param color: color to use, see :py:const:`waflib.Logs.colors_lst`
469
if not isinstance(color, str):
470
color = result and 'GREEN' or 'YELLOW'
472
self.end_msg(result, color)
474
def start_msg(self, msg):
476
Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
487
self.line_just = max(self.line_just, len(msg))
488
except AttributeError:
489
self.line_just = max(40, len(msg))
490
for x in (self.line_just * '-', msg):
492
Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
494
def end_msg(self, result, color=None):
495
"""Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
503
elif result == False:
510
Logs.pprint(color or defcolor, msg)
513
def load_special_tools(self, var, ban=[]):
515
lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
517
if not x.name in ban:
518
load_tool(x.name.replace('.py', ''))
522
Dictionary holding already loaded modules, keyed by their absolute path.
523
The modules are added automatically by :py:func:`waflib.Context.load_module`
526
def load_module(path):
528
Load a source file as a python module.
530
:param path: file path
532
:return: Loaded Python module
536
return cache_modules[path]
540
module = imp.new_module(WSCRIPT_FILE)
542
code = Utils.readf(path, m='rU')
543
except (IOError, OSError):
544
raise Errors.WafError('Could not read the file %r' % path)
546
module_dir = os.path.dirname(path)
547
sys.path.insert(0, module_dir)
549
exec(compile(code, path, 'exec'), module.__dict__)
550
sys.path.remove(module_dir)
552
cache_modules[path] = module
556
def load_tool(tool, tooldir=None):
558
Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools`
561
:param tool: Name of the tool
563
:param tooldir: List of directories to search for the tool module
565
tool = tool.replace('++', 'xx')
566
tool = tool.replace('java', 'javaw')
567
tool = tool.replace('compiler_cc', 'compiler_c')
570
assert isinstance(tooldir, list)
571
sys.path = tooldir + sys.path
574
ret = sys.modules[tool]
575
Context.tools[tool] = ret
583
os.stat(os.path.join(waf_dir, 'waflib', 'extras', tool + '.py'))
584
d = 'waflib.extras.%s' % tool
587
os.stat(os.path.join(waf_dir, 'waflib', 'Tools', tool + '.py'))
588
d = 'waflib.Tools.%s' % tool
590
d = tool # user has messed with sys.path
594
Context.tools[tool] = ret