Update copyright year in jackd
[jack2.git] / waflib / Context.py
blobb238636ac31553f90a435fd583bc8e0fc9561aeb
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2010 (ita)
5 """
6 Classes and functions required for waf commands
7 """
9 import os, re, imp, sys
10 from waflib import Utils, Errors, Logs
11 import waflib.Node
13 # the following 3 constants are updated on each new release (do not touch)
14 HEXVERSION=0x1081100
15 """Constant updated on new releases"""
17 WAFVERSION="1.8.17"
18 """Constant updated on new releases"""
20 WAFREVISION="cd7579a727d1b390bf9cbf111c1b20e811370bc0"
21 """Git revision when the waf version is updated"""
23 ABI = 98
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"""
29 APPNAME = 'APPNAME'
30 """Default application name (used by ``waf dist``)"""
32 VERSION = 'VERSION'
33 """Default application version (used by ``waf dist``)"""
35 TOP = 'top'
36 """The variable name for the top-level directory in wscript files"""
38 OUT = 'out'
39 """The variable name for the output directory in wscript files"""
41 WSCRIPT_FILE = 'wscript'
42 """Name of the waf script files"""
45 launch_dir = ''
46 """Directory from which waf has been called"""
47 run_dir = ''
48 """Location of the wscript file to use as the entry point"""
49 top_dir = ''
50 """Location of the project directory (top), if the project was configured"""
51 out_dir = ''
52 """Location of the build directory (out), if the project was configured"""
53 waf_dir = ''
54 """Directory containing the waf modules"""
56 local_repo = ''
57 """Local repository containing additional Waf tools (plugins)"""
58 remote_repo = 'https://raw.githubusercontent.com/waf-project/waf/master/'
59 """
60 Remote directory containing downloadable waf tools. The missing tools can be downloaded by using::
62 $ waf configure --download
63 """
65 remote_locs = ['waflib/extras', 'waflib/Tools']
66 """
67 Remote directories for use with :py:const:`waflib.Context.remote_repo`
68 """
70 g_module = None
71 """
72 Module representing the main wscript file (see :py:const:`waflib.Context.run_dir`)
73 """
75 STDOUT = 1
76 STDERR = -1
77 BOTH = 0
79 classes = []
80 """
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.
83 """
86 def create_context(cmd_name, *k, **kw):
87 """
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
94 :type k: list
95 :param k: keyword arguments to give to the context class initializer
96 :type k: dict
97 """
98 global classes
99 for x in classes:
100 if x.cmd == cmd_name:
101 return x(*k, **kw)
102 ctx = Context(*k, **kw)
103 ctx.fun = cmd_name
104 return ctx
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)
113 name = cls.__name__
115 if name == 'ctx' or name == 'Context':
116 return
118 try:
119 cls.cmd
120 except AttributeError:
121 raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
123 if not getattr(cls, 'fun', None):
124 cls.fun = cls.cmd
126 global classes
127 classes.insert(0, cls)
129 ctx = store_context('ctx', (object,), {})
130 """Base class for the :py:class:`waflib.Context.Context` classes"""
132 class Context(ctx):
134 Default context for waf commands, and base class for new command contexts.
136 Context objects are passed to top-level functions::
138 def foo(ctx):
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``
144 :type cmd: string
145 :param fun: function name to execute when the command is called
146 :type fun: string
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
152 errors = Errors
154 Shortcut to :py:mod:`waflib.Errors` provided for convenience
157 tools = {}
159 A cache for modules (wscript files) read by :py:meth:`Context.Context.load`
162 def __init__(self, **kw):
163 try:
164 rd = kw['run_dir']
165 except KeyError:
166 global run_dir
167 rd = run_dir
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)
178 self.stack_path = []
179 self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
180 self.logger = None
182 def __hash__(self):
184 Return a hash value for storing context objects in dicts or sets. The value is not persistent.
186 :return: hash value
187 :rtype: int
189 return id(self)
191 def finalize(self):
193 Use to free resources such as open files potentially held by the logger
195 try:
196 logger = self.logger
197 except AttributeError:
198 pass
199 else:
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)
215 for t in tools:
216 module = load_tool(t, path, with_sys_path=with_sys_path)
217 fun = getattr(module, kw.get('name', self.fun), None)
218 if fun:
219 fun(self)
221 def execute(self):
223 Execute the command. Redefine this method in subclasses.
225 global g_module
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``
233 :param node: script
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.
245 :param node: script
246 :type node: :py:class:`waflib.Node.Node`
248 self.cur_script = self.stack_path.pop()
249 if self.cur_script:
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
262 :type name: string
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
266 :type once: bool
268 try:
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):
284 cache[node] = True
285 self.pre_recurse(node)
286 try:
287 function_code = node.read('rU', encoding)
288 exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
289 finally:
290 self.post_recurse(node)
291 elif not 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):
295 cache[tup] = True
296 self.pre_recurse(node)
297 try:
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:
301 if not mandatory:
302 continue
303 raise Errors.WafError('No function %s defined in %s' % (name or self.fun, node.abspath()))
304 user_function(self)
305 finally:
306 self.post_recurse(node)
307 elif not node:
308 if not mandatory:
309 continue
310 try:
311 os.listdir(d)
312 except OSError:
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::
321 def run(tsk):
322 ret = tsk.generator.bld.exec_command('touch foo.txt')
323 return ret
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)
336 if self.logger:
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])
347 wargs = {}
348 if 'timeout' in kw:
349 if kw['timeout'] is not None:
350 wargs['timeout'] = kw['timeout']
351 del kw['timeout']
352 if 'input' in kw:
353 if kw['input']:
354 wargs['input'] = kw['input']
355 kw['stdin'] = Utils.subprocess.PIPE
356 del kw['input']
358 try:
359 if kw['stdout'] or kw['stderr']:
360 p = subprocess.Popen(cmd, **kw)
361 (out, err) = p.communicate(**wargs)
362 ret = p.returncode
363 else:
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)
369 if out:
370 if not isinstance(out, str):
371 out = out.decode(sys.stdout.encoding or 'iso8859-1')
372 if self.logger:
373 self.logger.debug('out: %s' % out)
374 else:
375 Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
376 if err:
377 if not isinstance(err, str):
378 err = err.decode(sys.stdout.encoding or 'iso8859-1')
379 if self.logger:
380 self.logger.error('err: %s' % err)
381 else:
382 Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
384 return ret
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::
392 def configure(conf):
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)
396 try:
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,))
408 if 'quiet' in kw:
409 quiet = kw['quiet']
410 del kw['quiet']
411 else:
412 quiet = None
414 if 'output' in kw:
415 to_ret = kw['output']
416 del kw['output']
417 else:
418 to_ret = STDOUT
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
424 if quiet is None:
425 self.to_log(cmd)
427 wargs = {}
428 if 'timeout' in kw:
429 if kw['timeout'] is not None:
430 wargs['timeout'] = kw['timeout']
431 del kw['timeout']
432 if 'input' in kw:
433 if kw['input']:
434 wargs['input'] = kw['input']
435 kw['stdin'] = Utils.subprocess.PIPE
436 del kw['input']
438 try:
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)
454 if p.returncode:
455 e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode))
456 e.returncode = p.returncode
457 e.stderr = err
458 e.stdout = out
459 raise e
461 if to_ret == BOTH:
462 return (out, err)
463 elif to_ret == STDERR:
464 return err
465 return out
467 def fatal(self, msg, ex=None):
469 Raise a configuration error to interrupt the execution immediately::
471 def configure(conf):
472 conf.fatal('a requirement is missing')
474 :param msg: message to display
475 :type msg: string
476 :param ex: optional exception object
477 :type ex: exception
479 if self.logger:
480 self.logger.info('from %s: %s' % (self.path.abspath(), msg))
481 try:
482 msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
483 except Exception:
484 pass
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,
490 it is not printed::
492 def build(bld):
493 bld.to_log('starting the build')
495 When in doubt, override this method, or provide a logger on the context class.
497 :param msg: message
498 :type msg: string
500 if not msg:
501 return
502 if self.logger:
503 self.logger.info(msg)
504 else:
505 sys.stderr.write(str(msg))
506 sys.stderr.flush()
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::
515 def configure(conf):
516 self.in_msg = 1
517 conf.msg('Checking for library foo', 'ok')
518 # no output
520 :param msg: message to display to the user
521 :type msg: string
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`
525 :type color: string
527 try:
528 msg = kw['msg']
529 except KeyError:
530 msg = k[0]
532 self.start_msg(msg, **kw)
534 try:
535 result = kw['result']
536 except KeyError:
537 result = k[1]
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):
550 return
552 msg = kw.get('msg', None) or k[0]
553 try:
554 if self.in_msg:
555 self.in_msg += 1
556 return
557 except AttributeError:
558 self.in_msg = 0
559 self.in_msg += 1
561 try:
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):
566 self.to_log(x)
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):
572 return
573 self.in_msg -= 1
574 if self.in_msg:
575 return
577 result = kw.get('result', None) or k[0]
579 defcolor = 'GREEN'
580 if result == True:
581 msg = 'ok'
582 elif result == False:
583 msg = 'not found'
584 defcolor = 'YELLOW'
585 else:
586 msg = str(result)
588 self.to_log(msg)
589 try:
590 color = kw['color']
591 except KeyError:
592 if len(k) > 1 and k[1] in Logs.colors_lst:
593 # compatibility waf 1.7
594 color = k[1]
595 else:
596 color = defcolor
597 Logs.pprint(color, msg)
599 def load_special_tools(self, var, ban=[]):
600 global waf_dir
601 if os.path.isdir(waf_dir):
602 lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
603 for x in lst:
604 if not x.name in ban:
605 load_tool(x.name.replace('.py', ''))
606 else:
607 from zipfile import PyZipFile
608 waflibs = PyZipFile(waf_dir)
609 lst = waflibs.namelist()
610 for x in lst:
611 if not re.match("waflib/extras/%s" % var.replace("*", ".*"), var):
612 continue
613 f = os.path.basename(x)
614 doban = False
615 for b in ban:
616 r = b.replace("*", ".*")
617 if re.match(r, f):
618 doban = True
619 if not doban:
620 f = f.replace('.py', '')
621 load_tool(f)
623 cache_modules = {}
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
634 :type path: string
635 :return: Loaded Python module
636 :rtype: module
638 try:
639 return cache_modules[path]
640 except KeyError:
641 pass
643 module = imp.new_module(WSCRIPT_FILE)
644 try:
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
657 return 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`
663 :type tool: string
664 :param tool: Name of the tool
665 :type tooldir: list
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
670 if tool == 'java':
671 tool = 'javaw' # jython
672 else:
673 tool = tool.replace('++', 'xx')
675 origSysPath = sys.path
676 if not with_sys_path: sys.path = []
677 try:
678 if tooldir:
679 assert isinstance(tooldir, list)
680 sys.path = tooldir + sys.path
681 try:
682 __import__(tool)
683 finally:
684 for d in tooldir:
685 sys.path.remove(d)
686 ret = sys.modules[tool]
687 Context.tools[tool] = ret
688 return ret
689 else:
690 if not with_sys_path: sys.path.insert(0, waf_dir)
691 try:
692 for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
693 try:
694 __import__(x % tool)
695 break
696 except ImportError:
697 x = None
698 if x is None: # raise an exception
699 __import__(tool)
700 finally:
701 if not with_sys_path: sys.path.remove(waf_dir)
702 ret = sys.modules[x % tool]
703 Context.tools[tool] = ret
704 return ret
705 finally:
706 if not with_sys_path: sys.path += origSysPath