3 # Thomas Nagy, 2010 (ita)
6 Classes and functions required for waf commands
9 import os
, re
, imp
, sys
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
="cd7579a727d1b390bf9cbf111c1b20e811370bc0"
21 """Git revision when the waf version is updated"""
24 """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
26 DBFILE
= '.wafpickle-%s-%d-%d' % (sys
.platform
, sys
.hexversion
, 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
= 'https://raw.githubusercontent.com/waf-project/waf/master/'
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 self
.node_class
= type("Nod3", (waflib
.Node
.Node
,), {})
171 self
.node_class
.__module
__ = "waflib.Node"
172 self
.node_class
.ctx
= self
174 self
.root
= self
.node_class('', None)
175 self
.cur_script
= None
176 self
.path
= self
.root
.find_dir(rd
)
179 self
.exec_dict
= {'ctx':self
, 'conf':self
, 'bld':self
, 'opt':self
}
184 Return a hash value for storing context objects in dicts or sets. The value is not persistent.
193 Use to free resources such as open files potentially held by the logger
197 except AttributeError:
200 Logs
.free_logger(logger
)
201 delattr(self
, 'logger')
203 def load(self
, tool_list
, *k
, **kw
):
205 Load a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun` from it.
206 A ``tooldir`` value may be provided as a list of module paths.
208 :type tool_list: list of string or space-separated string
209 :param tool_list: list of Waf tools to use
211 tools
= Utils
.to_list(tool_list
)
212 path
= Utils
.to_list(kw
.get('tooldir', ''))
213 with_sys_path
= kw
.get('with_sys_path', True)
216 module
= load_tool(t
, path
, with_sys_path
=with_sys_path
)
217 fun
= getattr(module
, kw
.get('name', self
.fun
), None)
223 Execute the command. Redefine this method in subclasses.
226 self
.recurse([os
.path
.dirname(g_module
.root_path
)])
228 def pre_recurse(self
, node
):
230 Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`. The node given is set
231 as an attribute ``self.cur_script``, and as the current path ``self.path``
234 :type node: :py:class:`waflib.Node.Node`
236 self
.stack_path
.append(self
.cur_script
)
238 self
.cur_script
= node
239 self
.path
= node
.parent
241 def post_recurse(self
, node
):
243 Restore ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
246 :type node: :py:class:`waflib.Node.Node`
248 self
.cur_script
= self
.stack_path
.pop()
250 self
.path
= self
.cur_script
.parent
252 def recurse(self
, dirs
, name
=None, mandatory
=True, once
=True, encoding
=None):
254 Run user code from the supplied list of directories.
255 The directories can be either absolute, or relative to the directory
256 of the wscript file. The methods :py:meth:`waflib.Context.Context.pre_recurse` and :py:meth:`waflib.Context.Context.post_recurse`
257 are called immediately before and after a script has been executed.
259 :param dirs: List of directories to visit
260 :type dirs: list of string or space-separated string
261 :param name: Name of function to invoke from the wscript
263 :param mandatory: whether sub wscript files are required to exist
264 :type mandatory: bool
265 :param once: read the script file once for a particular context
269 cache
= self
.recurse_cache
270 except AttributeError:
271 cache
= self
.recurse_cache
= {}
273 for d
in Utils
.to_list(dirs
):
275 if not os
.path
.isabs(d
):
276 # absolute paths only
277 d
= os
.path
.join(self
.path
.abspath(), d
)
279 WSCRIPT
= os
.path
.join(d
, WSCRIPT_FILE
)
280 WSCRIPT_FUN
= WSCRIPT
+ '_' + (name
or self
.fun
)
282 node
= self
.root
.find_node(WSCRIPT_FUN
)
283 if node
and (not once
or node
not in cache
):
285 self
.pre_recurse(node
)
287 function_code
= node
.read('rU', encoding
)
288 exec(compile(function_code
, node
.abspath(), 'exec'), self
.exec_dict
)
290 self
.post_recurse(node
)
292 node
= self
.root
.find_node(WSCRIPT
)
293 tup
= (node
, name
or self
.fun
)
294 if node
and (not once
or tup
not in cache
):
296 self
.pre_recurse(node
)
298 wscript_module
= load_module(node
.abspath(), encoding
=encoding
)
299 user_function
= getattr(wscript_module
, (name
or self
.fun
), None)
300 if not user_function
:
303 raise Errors
.WafError('No function %s defined in %s' % (name
or self
.fun
, node
.abspath()))
306 self
.post_recurse(node
)
313 raise Errors
.WafError('Cannot read the folder %r' % d
)
314 raise Errors
.WafError('No wscript file in directory %s' % d
)
316 def exec_command(self
, cmd
, **kw
):
318 Execute a command and return the exit status. If the context has the attribute 'log',
319 capture and log the process stderr/stdout for logging purposes::
322 ret = tsk.generator.bld.exec_command('touch foo.txt')
325 This method captures the standard/error outputs (Issue 1101), but it does not return the values
326 unlike :py:meth:`waflib.Context.Context.cmd_and_log`
328 :param cmd: command argument for subprocess.Popen
329 :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
331 subprocess
= Utils
.subprocess
332 kw
['shell'] = isinstance(cmd
, str)
333 Logs
.debug('runner: %r' % (cmd
,))
334 Logs
.debug('runner_env: kw=%s' % kw
)
337 self
.logger
.info(cmd
)
339 if 'stdout' not in kw
:
340 kw
['stdout'] = subprocess
.PIPE
341 if 'stderr' not in kw
:
342 kw
['stderr'] = subprocess
.PIPE
344 if Logs
.verbose
and not kw
['shell'] and not Utils
.check_exe(cmd
[0]):
345 raise Errors
.WafError("Program %s not found!" % cmd
[0])
349 if kw
['timeout'] is not None:
350 wargs
['timeout'] = kw
['timeout']
354 wargs
['input'] = kw
['input']
355 kw
['stdin'] = Utils
.subprocess
.PIPE
359 if kw
['stdout'] or kw
['stderr']:
360 p
= subprocess
.Popen(cmd
, **kw
)
361 (out
, err
) = p
.communicate(**wargs
)
364 out
, err
= (None, None)
365 ret
= subprocess
.Popen(cmd
, **kw
).wait(**wargs
)
366 except Exception as e
:
367 raise Errors
.WafError('Execution failure: %s' % str(e
), ex
=e
)
370 if not isinstance(out
, str):
371 out
= out
.decode(sys
.stdout
.encoding
or 'iso8859-1')
373 self
.logger
.debug('out: %s' % out
)
375 Logs
.info(out
, extra
={'stream':sys
.stdout
, 'c1': ''})
377 if not isinstance(err
, str):
378 err
= err
.decode(sys
.stdout
.encoding
or 'iso8859-1')
380 self
.logger
.error('err: %s' % err
)
382 Logs
.info(err
, extra
={'stream':sys
.stderr
, 'c1': ''})
386 def cmd_and_log(self
, cmd
, **kw
):
388 Execute a command and return stdout/stderr if the execution is successful.
389 An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
390 will be bound to the WafError object::
393 out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
394 (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
395 (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
397 conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
398 except Exception as e:
399 print(e.stdout, e.stderr)
401 :param cmd: args for subprocess.Popen
402 :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
404 subprocess
= Utils
.subprocess
405 kw
['shell'] = isinstance(cmd
, str)
406 Logs
.debug('runner: %r' % (cmd
,))
415 to_ret
= kw
['output']
420 if Logs
.verbose
and not kw
['shell'] and not Utils
.check_exe(cmd
[0]):
421 raise Errors
.WafError("Program %s not found!" % cmd
[0])
423 kw
['stdout'] = kw
['stderr'] = subprocess
.PIPE
429 if kw
['timeout'] is not None:
430 wargs
['timeout'] = kw
['timeout']
434 wargs
['input'] = kw
['input']
435 kw
['stdin'] = Utils
.subprocess
.PIPE
439 p
= subprocess
.Popen(cmd
, **kw
)
440 (out
, err
) = p
.communicate(**wargs
)
441 except Exception as e
:
442 raise Errors
.WafError('Execution failure: %s' % str(e
), ex
=e
)
444 if not isinstance(out
, str):
445 out
= out
.decode(sys
.stdout
.encoding
or 'iso8859-1')
446 if not isinstance(err
, str):
447 err
= err
.decode(sys
.stdout
.encoding
or 'iso8859-1')
449 if out
and quiet
!= STDOUT
and quiet
!= BOTH
:
450 self
.to_log('out: %s' % out
)
451 if err
and quiet
!= STDERR
and quiet
!= BOTH
:
452 self
.to_log('err: %s' % err
)
455 e
= Errors
.WafError('Command %r returned %r' % (cmd
, p
.returncode
))
456 e
.returncode
= p
.returncode
463 elif to_ret
== STDERR
:
467 def fatal(self
, msg
, ex
=None):
469 Raise a configuration error to interrupt the execution immediately::
472 conf.fatal('a requirement is missing')
474 :param msg: message to display
476 :param ex: optional exception object
480 self
.logger
.info('from %s: %s' % (self
.path
.abspath(), msg
))
482 msg
= '%s\n(complete log in %s)' % (msg
, self
.logger
.handlers
[0].baseFilename
)
485 raise self
.errors
.ConfigurationError(msg
, ex
=ex
)
487 def to_log(self
, msg
):
489 Log some information to the logger (if present), or to stderr. If the message is empty,
493 bld.to_log('starting the build')
495 When in doubt, override this method, or provide a logger on the context class.
503 self
.logger
.info(msg
)
505 sys
.stderr
.write(str(msg
))
509 def msg(self
, *k
, **kw
):
511 Print a configuration message of the form ``msg: result``.
512 The second part of the message will be in colors. The output
513 can be disabled easly by setting ``in_msg`` to a positive value::
517 conf.msg('Checking for library foo', 'ok')
520 :param msg: message to display to the user
522 :param result: result to display
523 :type result: string or boolean
524 :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
532 self
.start_msg(msg
, **kw
)
535 result
= kw
['result']
539 color
= kw
.get('color', None)
540 if not isinstance(color
, str):
541 color
= result
and 'GREEN' or 'YELLOW'
543 self
.end_msg(result
, color
, **kw
)
545 def start_msg(self
, *k
, **kw
):
547 Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
549 if kw
.get('quiet', None):
552 msg
= kw
.get('msg', None) or k
[0]
557 except AttributeError:
562 self
.line_just
= max(self
.line_just
, len(msg
))
563 except AttributeError:
564 self
.line_just
= max(40, len(msg
))
565 for x
in (self
.line_just
* '-', msg
):
567 Logs
.pprint('NORMAL', "%s :" % msg
.ljust(self
.line_just
), sep
='')
569 def end_msg(self
, *k
, **kw
):
570 """Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
571 if kw
.get('quiet', None):
577 result
= kw
.get('result', None) or k
[0]
582 elif result
== False:
592 if len(k
) > 1 and k
[1] in Logs
.colors_lst
:
593 # compatibility waf 1.7
597 Logs
.pprint(color
, msg
)
599 def load_special_tools(self
, var
, ban
=[]):
601 if os
.path
.isdir(waf_dir
):
602 lst
= self
.root
.find_node(waf_dir
).find_node('waflib/extras').ant_glob(var
)
604 if not x
.name
in ban
:
605 load_tool(x
.name
.replace('.py', ''))
607 from zipfile
import PyZipFile
608 waflibs
= PyZipFile(waf_dir
)
609 lst
= waflibs
.namelist()
611 if not re
.match("waflib/extras/%s" % var
.replace("*", ".*"), var
):
613 f
= os
.path
.basename(x
)
616 r
= b
.replace("*", ".*")
620 f
= f
.replace('.py', '')
625 Dictionary holding already loaded modules, keyed by their absolute path.
626 The modules are added automatically by :py:func:`waflib.Context.load_module`
629 def load_module(path
, encoding
=None):
631 Load a source file as a python module.
633 :param path: file path
635 :return: Loaded Python module
639 return cache_modules
[path
]
643 module
= imp
.new_module(WSCRIPT_FILE
)
645 code
= Utils
.readf(path
, m
='rU', encoding
=encoding
)
646 except EnvironmentError:
647 raise Errors
.WafError('Could not read the file %r' % path
)
649 module_dir
= os
.path
.dirname(path
)
650 sys
.path
.insert(0, module_dir
)
652 try : exec(compile(code
, path
, 'exec'), module
.__dict
__)
653 finally: sys
.path
.remove(module_dir
)
655 cache_modules
[path
] = module
659 def load_tool(tool
, tooldir
=None, ctx
=None, with_sys_path
=True):
661 Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools`
664 :param tool: Name of the tool
666 :param tooldir: List of directories to search for the tool module
667 :type with_sys_path: boolean
668 :param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
671 tool
= 'javaw' # jython
673 tool
= tool
.replace('++', 'xx')
675 origSysPath
= sys
.path
676 if not with_sys_path
: sys
.path
= []
679 assert isinstance(tooldir
, list)
680 sys
.path
= tooldir
+ sys
.path
686 ret
= sys
.modules
[tool
]
687 Context
.tools
[tool
] = ret
690 if not with_sys_path
: sys
.path
.insert(0, waf_dir
)
692 for x
in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
698 if x
is None: # raise an exception
701 if not with_sys_path
: sys
.path
.remove(waf_dir
)
702 ret
= sys
.modules
[x
% tool
]
703 Context
.tools
[tool
] = ret
706 if not with_sys_path
: sys
.path
+= origSysPath