third_party/waf: upgrade to waf 2.0.8
[vlendec/samba-autobuild/.git] / third_party / waf / waflib / Configure.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2018 (ita)
4
5 """
6 Configuration system
7
8 A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``waf configure`` is called, it is used to:
9
10 * create data dictionaries (ConfigSet instances)
11 * store the list of modules to import
12 * hold configuration routines such as ``find_program``, etc
13 """
14
15 import os, re, shlex, shutil, sys, time, traceback
16 from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors
17
18 WAF_CONFIG_LOG = 'config.log'
19 """Name of the configuration log file"""
20
21 autoconfig = False
22 """Execute the configuration automatically"""
23
24 conf_template = '''# project %(app)s configured on %(now)s by
25 # waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
26 # using %(args)s
27 #'''
28
29 class ConfigurationContext(Context.Context):
30         '''configures the project'''
31
32         cmd = 'configure'
33
34         error_handlers = []
35         """
36         Additional functions to handle configuration errors
37         """
38
39         def __init__(self, **kw):
40                 super(ConfigurationContext, self).__init__(**kw)
41                 self.environ = dict(os.environ)
42                 self.all_envs = {}
43
44                 self.top_dir = None
45                 self.out_dir = None
46
47                 self.tools = [] # tools loaded in the configuration, and that will be loaded when building
48
49                 self.hash = 0
50                 self.files = []
51
52                 self.tool_cache = []
53
54                 self.setenv('')
55
56         def setenv(self, name, env=None):
57                 """
58                 Set a new config set for conf.env. If a config set of that name already exists,
59                 recall it without modification.
60
61                 The name is the filename prefix to save to ``c4che/NAME_cache.py``, and it
62                 is also used as *variants* by the build commands.
63                 Though related to variants, whatever kind of data may be stored in the config set::
64
65                         def configure(cfg):
66                                 cfg.env.ONE = 1
67                                 cfg.setenv('foo')
68                                 cfg.env.ONE = 2
69
70                         def build(bld):
71                                 2 == bld.env_of_name('foo').ONE
72
73                 :param name: name of the configuration set
74                 :type name: string
75                 :param env: ConfigSet to copy, or an empty ConfigSet is created
76                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
77                 """
78                 if name not in self.all_envs or env:
79                         if not env:
80                                 env = ConfigSet.ConfigSet()
81                                 self.prepare_env(env)
82                         else:
83                                 env = env.derive()
84                         self.all_envs[name] = env
85                 self.variant = name
86
87         def get_env(self):
88                 """Getter for the env property"""
89                 return self.all_envs[self.variant]
90         def set_env(self, val):
91                 """Setter for the env property"""
92                 self.all_envs[self.variant] = val
93
94         env = property(get_env, set_env)
95
96         def init_dirs(self):
97                 """
98                 Initialize the project directory and the build directory
99                 """
100
101                 top = self.top_dir
102                 if not top:
103                         top = Options.options.top
104                 if not top:
105                         top = getattr(Context.g_module, Context.TOP, None)
106                 if not top:
107                         top = self.path.abspath()
108                 top = os.path.abspath(top)
109
110                 self.srcnode = (os.path.isabs(top) and self.root or self.path).find_dir(top)
111                 assert(self.srcnode)
112
113                 out = self.out_dir
114                 if not out:
115                         out = Options.options.out
116                 if not out:
117                         out = getattr(Context.g_module, Context.OUT, None)
118                 if not out:
119                         out = Options.lockfile.replace('.lock-waf_%s_' % sys.platform, '').replace('.lock-waf', '')
120
121                 # someone can be messing with symlinks
122                 out = os.path.realpath(out)
123
124                 self.bldnode = (os.path.isabs(out) and self.root or self.path).make_node(out)
125                 self.bldnode.mkdir()
126
127                 if not os.path.isdir(self.bldnode.abspath()):
128                         conf.fatal('Could not create the build directory %s' % self.bldnode.abspath())
129
130         def execute(self):
131                 """
132                 See :py:func:`waflib.Context.Context.execute`
133                 """
134                 self.init_dirs()
135
136                 self.cachedir = self.bldnode.make_node(Build.CACHE_DIR)
137                 self.cachedir.mkdir()
138
139                 path = os.path.join(self.bldnode.abspath(), WAF_CONFIG_LOG)
140                 self.logger = Logs.make_logger(path, 'cfg')
141
142                 app = getattr(Context.g_module, 'APPNAME', '')
143                 if app:
144                         ver = getattr(Context.g_module, 'VERSION', '')
145                         if ver:
146                                 app = "%s (%s)" % (app, ver)
147
148                 params = {'now': time.ctime(), 'pyver': sys.hexversion, 'systype': sys.platform, 'args': " ".join(sys.argv), 'wafver': Context.WAFVERSION, 'abi': Context.ABI, 'app': app}
149                 self.to_log(conf_template % params)
150                 self.msg('Setting top to', self.srcnode.abspath())
151                 self.msg('Setting out to', self.bldnode.abspath())
152
153                 if id(self.srcnode) == id(self.bldnode):
154                         Logs.warn('Setting top == out')
155                 elif id(self.path) != id(self.srcnode):
156                         if self.srcnode.is_child_of(self.path):
157                                 Logs.warn('Are you certain that you do not want to set top="." ?')
158
159                 super(ConfigurationContext, self).execute()
160
161                 self.store()
162
163                 Context.top_dir = self.srcnode.abspath()
164                 Context.out_dir = self.bldnode.abspath()
165
166                 # this will write a configure lock so that subsequent builds will
167                 # consider the current path as the root directory (see prepare_impl).
168                 # to remove: use 'waf distclean'
169                 env = ConfigSet.ConfigSet()
170                 env.argv = sys.argv
171                 env.options = Options.options.__dict__
172                 env.config_cmd = self.cmd
173
174                 env.run_dir = Context.run_dir
175                 env.top_dir = Context.top_dir
176                 env.out_dir = Context.out_dir
177
178                 # conf.hash & conf.files hold wscript files paths and hash
179                 # (used only by Configure.autoconfig)
180                 env.hash = self.hash
181                 env.files = self.files
182                 env.environ = dict(self.environ)
183
184                 if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')):
185                         env.store(os.path.join(Context.run_dir, Options.lockfile))
186                 if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')):
187                         env.store(os.path.join(Context.top_dir, Options.lockfile))
188                 if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')):
189                         env.store(os.path.join(Context.out_dir, Options.lockfile))
190
191         def prepare_env(self, env):
192                 """
193                 Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
194
195                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
196                 :param env: a ConfigSet, usually ``conf.env``
197                 """
198                 if not env.PREFIX:
199                         if Options.options.prefix or Utils.is_win32:
200                                 env.PREFIX = Options.options.prefix
201                         else:
202                                 env.PREFIX = '/'
203                 if not env.BINDIR:
204                         if Options.options.bindir:
205                                 env.BINDIR = Options.options.bindir
206                         else:
207                                 env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
208                 if not env.LIBDIR:
209                         if Options.options.libdir:
210                                 env.LIBDIR = Options.options.libdir
211                         else:
212                                 env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env)
213
214         def store(self):
215                 """Save the config results into the cache file"""
216                 n = self.cachedir.make_node('build.config.py')
217                 n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools))
218
219                 if not self.all_envs:
220                         self.fatal('nothing to store in the configuration context!')
221
222                 for key in self.all_envs:
223                         tmpenv = self.all_envs[key]
224                         tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))
225
226         def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False):
227                 """
228                 Load Waf tools, which will be imported whenever a build is started.
229
230                 :param tool_list: waf tools to import
231                 :type tool_list: list of string
232                 :param tooldir: paths for the imports
233                 :type tooldir: list of string
234                 :param funs: functions to execute from the waf tools
235                 :type funs: list of string
236                 :param cache: whether to prevent the tool from running twice
237                 :type cache: bool
238                 """
239
240                 tools = Utils.to_list(tool_list)
241                 if tooldir:
242                         tooldir = Utils.to_list(tooldir)
243                 for tool in tools:
244                         # avoid loading the same tool more than once with the same functions
245                         # used by composite projects
246
247                         if cache:
248                                 mag = (tool, id(self.env), tooldir, funs)
249                                 if mag in self.tool_cache:
250                                         self.to_log('(tool %s is already loaded, skipping)' % tool)
251                                         continue
252                                 self.tool_cache.append(mag)
253
254                         module = None
255                         try:
256                                 module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path)
257                         except ImportError as e:
258                                 self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e))
259                         except Exception as e:
260                                 self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
261                                 self.to_log(traceback.format_exc())
262                                 raise
263
264                         if funs is not None:
265                                 self.eval_rules(funs)
266                         else:
267                                 func = getattr(module, 'configure', None)
268                                 if func:
269                                         if type(func) is type(Utils.readf):
270                                                 func(self)
271                                         else:
272                                                 self.eval_rules(func)
273
274                         self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
275
276         def post_recurse(self, node):
277                 """
278                 Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
279
280                 :param node: script
281                 :type node: :py:class:`waflib.Node.Node`
282                 """
283                 super(ConfigurationContext, self).post_recurse(node)
284                 self.hash = Utils.h_list((self.hash, node.read('rb')))
285                 self.files.append(node.abspath())
286
287         def eval_rules(self, rules):
288                 """
289                 Execute configuration tests provided as list of funcitons to run
290
291                 :param rules: list of configuration method names
292                 :type rules: list of string
293                 """
294                 self.rules = Utils.to_list(rules)
295                 for x in self.rules:
296                         f = getattr(self, x)
297                         if not f:
298                                 self.fatal('No such configuration function %r' % x)
299                         f()
300
301 def conf(f):
302         """
303         Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and
304         :py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter
305         named 'mandatory' to disable the configuration errors::
306
307                 def configure(conf):
308                         conf.find_program('abc', mandatory=False)
309
310         :param f: method to bind
311         :type f: function
312         """
313         def fun(*k, **kw):
314                 mandatory = kw.pop('mandatory', True)
315                 try:
316                         return f(*k, **kw)
317                 except Errors.ConfigurationError:
318                         if mandatory:
319                                 raise
320
321         fun.__name__ = f.__name__
322         setattr(ConfigurationContext, f.__name__, fun)
323         setattr(Build.BuildContext, f.__name__, fun)
324         return f
325
326 @conf
327 def add_os_flags(self, var, dest=None, dup=False):
328         """
329         Import operating system environment values into ``conf.env`` dict::
330
331                 def configure(conf):
332                         conf.add_os_flags('CFLAGS')
333
334         :param var: variable to use
335         :type var: string
336         :param dest: destination variable, by default the same as var
337         :type dest: string
338         :param dup: add the same set of flags again
339         :type dup: bool
340         """
341         try:
342                 flags = shlex.split(self.environ[var])
343         except KeyError:
344                 return
345         if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])):
346                 self.env.append_value(dest or var, flags)
347
348 @conf
349 def cmd_to_list(self, cmd):
350         """
351         Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
352
353         :param cmd: command
354         :type cmd: a string or a list of string
355         """
356         if isinstance(cmd, str):
357                 if os.path.isfile(cmd):
358                         # do not take any risk
359                         return [cmd]
360                 if os.sep == '/':
361                         return shlex.split(cmd)
362                 else:
363                         try:
364                                 return shlex.split(cmd, posix=False)
365                         except TypeError:
366                                 # Python 2.5 on windows?
367                                 return shlex.split(cmd)
368         return cmd
369
370 @conf
371 def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw):
372         """
373         Raise a Configuration error if the Waf version does not strictly match the given bounds::
374
375                 conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
376
377         :type  mini: number, tuple or string
378         :param mini: Minimum required version
379         :type  maxi: number, tuple or string
380         :param maxi: Maximum allowed version
381         """
382         self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)), **kw)
383         ver = Context.HEXVERSION
384         if Utils.num2ver(mini) > ver:
385                 self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver))
386         if Utils.num2ver(maxi) < ver:
387                 self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver))
388         self.end_msg('ok', **kw)
389
390 @conf
391 def find_file(self, filename, path_list=[]):
392         """
393         Find a file in a list of paths
394
395         :param filename: name of the file to search for
396         :param path_list: list of directories to search
397         :return: the first matching filename; else a configuration exception is raised
398         """
399         for n in Utils.to_list(filename):
400                 for d in Utils.to_list(path_list):
401                         p = os.path.expanduser(os.path.join(d, n))
402                         if os.path.exists(p):
403                                 return p
404         self.fatal('Could not find %r' % filename)
405
406 @conf
407 def find_program(self, filename, **kw):
408         """
409         Search for a program on the operating system
410
411         When var is used, you may set os.environ[var] to help find a specific program version, for example::
412
413                 $ CC='ccache gcc' waf configure
414
415         :param path_list: paths to use for searching
416         :type param_list: list of string
417         :param var: store the result to conf.env[var] where var defaults to filename.upper() if not provided; the result is stored as a list of strings
418         :type var: string
419         :param value: obtain the program from the value passed exclusively
420         :type value: list or string (list is preferred)
421         :param exts: list of extensions for the binary (do not add an extension for portability)
422         :type exts: list of string
423         :param msg: name to display in the log, by default filename is used
424         :type msg: string
425         :param interpreter: interpreter for the program
426         :type interpreter: ConfigSet variable key
427         :raises: :py:class:`waflib.Errors.ConfigurationError`
428         """
429
430         exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
431
432         environ = kw.get('environ', getattr(self, 'environ', os.environ))
433
434         ret = ''
435
436         filename = Utils.to_list(filename)
437         msg = kw.get('msg', ', '.join(filename))
438
439         var = kw.get('var', '')
440         if not var:
441                 var = re.sub(r'[-.]', '_', filename[0].upper())
442
443         path_list = kw.get('path_list', '')
444         if path_list:
445                 path_list = Utils.to_list(path_list)
446         else:
447                 path_list = environ.get('PATH', '').split(os.pathsep)
448
449         if kw.get('value'):
450                 # user-provided in command-line options and passed to find_program
451                 ret = self.cmd_to_list(kw['value'])
452         elif environ.get(var):
453                 # user-provided in the os environment
454                 ret = self.cmd_to_list(environ[var])
455         elif self.env[var]:
456                 # a default option in the wscript file
457                 ret = self.cmd_to_list(self.env[var])
458         else:
459                 if not ret:
460                         ret = self.find_binary(filename, exts.split(','), path_list)
461                 if not ret and Utils.winreg:
462                         ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename)
463                 if not ret and Utils.winreg:
464                         ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename)
465                 ret = self.cmd_to_list(ret)
466
467         if ret:
468                 if len(ret) == 1:
469                         retmsg = ret[0]
470                 else:
471                         retmsg = ret
472         else:
473                 retmsg = False
474
475         self.msg('Checking for program %r' % msg, retmsg, **kw)
476         if not kw.get('quiet'):
477                 self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret))
478
479         if not ret:
480                 self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename)
481
482         interpreter = kw.get('interpreter')
483         if interpreter is None:
484                 if not Utils.check_exe(ret[0], env=environ):
485                         self.fatal('Program %r is not executable' % ret)
486                 self.env[var] = ret
487         else:
488                 self.env[var] = self.env[interpreter] + ret
489
490         return ret
491
492 @conf
493 def find_binary(self, filenames, exts, paths):
494         for f in filenames:
495                 for ext in exts:
496                         exe_name = f + ext
497                         if os.path.isabs(exe_name):
498                                 if os.path.isfile(exe_name):
499                                         return exe_name
500                         else:
501                                 for path in paths:
502                                         x = os.path.expanduser(os.path.join(path, exe_name))
503                                         if os.path.isfile(x):
504                                                 return x
505         return None
506
507 @conf
508 def run_build(self, *k, **kw):
509         """
510         Create a temporary build context to execute a build. A reference to that build
511         context is kept on self.test_bld for debugging purposes, and you should not rely
512         on it too much (read the note on the cache below).
513         The parameters given in the arguments to this function are passed as arguments for
514         a single task generator created in the build. Only three parameters are obligatory:
515
516         :param features: features to pass to a task generator created in the build
517         :type features: list of string
518         :param compile_filename: file to create for the compilation (default: *test.c*)
519         :type compile_filename: string
520         :param code: code to write in the filename to compile
521         :type code: string
522
523         Though this function returns *0* by default, the build may set an attribute named *retval* on the
524         build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
525
526         This function also provides a limited cache. To use it, provide the following option::
527
528                 def options(opt):
529                         opt.add_option('--confcache', dest='confcache', default=0,
530                                 action='count', help='Use a configuration cache')
531
532         And execute the configuration with the following command-line::
533
534                 $ waf configure --confcache
535
536         """
537         lst = [str(v) for (p, v) in kw.items() if p != 'env']
538         h = Utils.h_list(lst)
539         dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
540
541         try:
542                 os.makedirs(dir)
543         except OSError:
544                 pass
545
546         try:
547                 os.stat(dir)
548         except OSError:
549                 self.fatal('cannot use the configuration test folder %r' % dir)
550
551         cachemode = getattr(Options.options, 'confcache', None)
552         if cachemode == 1:
553                 try:
554                         proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
555                 except EnvironmentError:
556                         pass
557                 else:
558                         ret = proj['cache_run_build']
559                         if isinstance(ret, str) and ret.startswith('Test does not build'):
560                                 self.fatal(ret)
561                         return ret
562
563         bdir = os.path.join(dir, 'testbuild')
564
565         if not os.path.exists(bdir):
566                 os.makedirs(bdir)
567
568         cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build')
569         self.test_bld = bld = Context.create_context(cls_name, top_dir=dir, out_dir=bdir)
570         bld.init_dirs()
571         bld.progress_bar = 0
572         bld.targets = '*'
573
574         bld.logger = self.logger
575         bld.all_envs.update(self.all_envs) # not really necessary
576         bld.env = kw['env']
577
578         bld.kw = kw
579         bld.conf = self
580         kw['build_fun'](bld)
581         ret = -1
582         try:
583                 try:
584                         bld.compile()
585                 except Errors.WafError:
586                         ret = 'Test does not build: %s' % traceback.format_exc()
587                         self.fatal(ret)
588                 else:
589                         ret = getattr(bld, 'retval', 0)
590         finally:
591                 if cachemode == 1:
592                         # cache the results each time
593                         proj = ConfigSet.ConfigSet()
594                         proj['cache_run_build'] = ret
595                         proj.store(os.path.join(dir, 'cache_run_build'))
596                 else:
597                         shutil.rmtree(dir)
598         return ret
599
600 @conf
601 def ret_msg(self, msg, args):
602         if isinstance(msg, str):
603                 return msg
604         return msg(args)
605
606 @conf
607 def test(self, *k, **kw):
608
609         if not 'env' in kw:
610                 kw['env'] = self.env.derive()
611
612         # validate_c for example
613         if kw.get('validate'):
614                 kw['validate'](kw)
615
616         self.start_msg(kw['msg'], **kw)
617         ret = None
618         try:
619                 ret = self.run_build(*k, **kw)
620         except self.errors.ConfigurationError:
621                 self.end_msg(kw['errmsg'], 'YELLOW', **kw)
622                 if Logs.verbose > 1:
623                         raise
624                 else:
625                         self.fatal('The configuration failed')
626         else:
627                 kw['success'] = ret
628
629         if kw.get('post_check'):
630                 ret = kw['post_check'](kw)
631
632         if ret:
633                 self.end_msg(kw['errmsg'], 'YELLOW', **kw)
634                 self.fatal('The configuration failed %r' % ret)
635         else:
636                 self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
637         return ret
638