1#!/usr/bin/env python 2# encoding: utf-8 3# Thomas Nagy, 2005-2018 (ita) 4 5""" 6Configuration system 7 8A :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 15import os, re, shlex, shutil, sys, time, traceback 16from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors 17 18WAF_CONFIG_LOG = 'config.log' 19"""Name of the configuration log file""" 20 21autoconfig = False 22"""Execute the configuration automatically""" 23 24conf_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 29class 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 functions 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 301def 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 327def 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 349def 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 371def 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 391def 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 407def 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 493def 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 508def 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 601def ret_msg(self, msg, args): 602 if isinstance(msg, str): 603 return msg 604 return msg(args) 605 606@conf 607def 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 639