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 self.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 env.launch_dir = Context.launch_dir 184 185 if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')): 186 env.store(os.path.join(Context.run_dir, Options.lockfile)) 187 if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')): 188 env.store(os.path.join(Context.top_dir, Options.lockfile)) 189 if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')): 190 env.store(os.path.join(Context.out_dir, Options.lockfile)) 191 192 def prepare_env(self, env): 193 """ 194 Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env`` 195 196 :type env: :py:class:`waflib.ConfigSet.ConfigSet` 197 :param env: a ConfigSet, usually ``conf.env`` 198 """ 199 if not env.PREFIX: 200 if Options.options.prefix or Utils.is_win32: 201 env.PREFIX = Options.options.prefix 202 else: 203 env.PREFIX = '/' 204 if not env.BINDIR: 205 if Options.options.bindir: 206 env.BINDIR = Options.options.bindir 207 else: 208 env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env) 209 if not env.LIBDIR: 210 if Options.options.libdir: 211 env.LIBDIR = Options.options.libdir 212 else: 213 env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env) 214 215 def store(self): 216 """Save the config results into the cache file""" 217 n = self.cachedir.make_node('build.config.py') 218 n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools)) 219 220 if not self.all_envs: 221 self.fatal('nothing to store in the configuration context!') 222 223 for key in self.all_envs: 224 tmpenv = self.all_envs[key] 225 tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX)) 226 227 def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False): 228 """ 229 Load Waf tools, which will be imported whenever a build is started. 230 231 :param tool_list: waf tools to import 232 :type tool_list: list of string 233 :param tooldir: paths for the imports 234 :type tooldir: list of string 235 :param funs: functions to execute from the waf tools 236 :type funs: list of string 237 :param cache: whether to prevent the tool from running twice 238 :type cache: bool 239 """ 240 241 tools = Utils.to_list(tool_list) 242 if tooldir: 243 tooldir = Utils.to_list(tooldir) 244 for tool in tools: 245 # avoid loading the same tool more than once with the same functions 246 # used by composite projects 247 248 if cache: 249 mag = (tool, id(self.env), tooldir, funs) 250 if mag in self.tool_cache: 251 self.to_log('(tool %s is already loaded, skipping)' % tool) 252 continue 253 self.tool_cache.append(mag) 254 255 module = None 256 try: 257 module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path) 258 except ImportError as e: 259 self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e)) 260 except Exception as e: 261 self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs)) 262 self.to_log(traceback.format_exc()) 263 raise 264 265 if funs is not None: 266 self.eval_rules(funs) 267 else: 268 func = getattr(module, 'configure', None) 269 if func: 270 if type(func) is type(Utils.readf): 271 func(self) 272 else: 273 self.eval_rules(func) 274 275 self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs}) 276 277 def post_recurse(self, node): 278 """ 279 Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse` 280 281 :param node: script 282 :type node: :py:class:`waflib.Node.Node` 283 """ 284 super(ConfigurationContext, self).post_recurse(node) 285 self.hash = Utils.h_list((self.hash, node.read('rb'))) 286 self.files.append(node.abspath()) 287 288 def eval_rules(self, rules): 289 """ 290 Execute configuration tests provided as list of functions to run 291 292 :param rules: list of configuration method names 293 :type rules: list of string 294 """ 295 self.rules = Utils.to_list(rules) 296 for x in self.rules: 297 f = getattr(self, x) 298 if not f: 299 self.fatal('No such configuration function %r' % x) 300 f() 301 302def conf(f): 303 """ 304 Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and 305 :py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter 306 named 'mandatory' to disable the configuration errors:: 307 308 def configure(conf): 309 conf.find_program('abc', mandatory=False) 310 311 :param f: method to bind 312 :type f: function 313 """ 314 def fun(*k, **kw): 315 mandatory = kw.pop('mandatory', True) 316 try: 317 return f(*k, **kw) 318 except Errors.ConfigurationError: 319 if mandatory: 320 raise 321 322 fun.__name__ = f.__name__ 323 setattr(ConfigurationContext, f.__name__, fun) 324 setattr(Build.BuildContext, f.__name__, fun) 325 return f 326 327@conf 328def add_os_flags(self, var, dest=None, dup=False): 329 """ 330 Import operating system environment values into ``conf.env`` dict:: 331 332 def configure(conf): 333 conf.add_os_flags('CFLAGS') 334 335 :param var: variable to use 336 :type var: string 337 :param dest: destination variable, by default the same as var 338 :type dest: string 339 :param dup: add the same set of flags again 340 :type dup: bool 341 """ 342 try: 343 flags = shlex.split(self.environ[var]) 344 except KeyError: 345 return 346 if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])): 347 self.env.append_value(dest or var, flags) 348 349@conf 350def cmd_to_list(self, cmd): 351 """ 352 Detect if a command is written in pseudo shell like ``ccache g++`` and return a list. 353 354 :param cmd: command 355 :type cmd: a string or a list of string 356 """ 357 if isinstance(cmd, str): 358 if os.path.isfile(cmd): 359 # do not take any risk 360 return [cmd] 361 if os.sep == '/': 362 return shlex.split(cmd) 363 else: 364 try: 365 return shlex.split(cmd, posix=False) 366 except TypeError: 367 # Python 2.5 on windows? 368 return shlex.split(cmd) 369 return cmd 370 371@conf 372def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw): 373 """ 374 Raise a Configuration error if the Waf version does not strictly match the given bounds:: 375 376 conf.check_waf_version(mini='1.9.99', maxi='2.1.0') 377 378 :type mini: number, tuple or string 379 :param mini: Minimum required version 380 :type maxi: number, tuple or string 381 :param maxi: Maximum allowed version 382 """ 383 self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)), **kw) 384 ver = Context.HEXVERSION 385 if Utils.num2ver(mini) > ver: 386 self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver)) 387 if Utils.num2ver(maxi) < ver: 388 self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver)) 389 self.end_msg('ok', **kw) 390 391@conf 392def find_file(self, filename, path_list=[]): 393 """ 394 Find a file in a list of paths 395 396 :param filename: name of the file to search for 397 :param path_list: list of directories to search 398 :return: the first matching filename; else a configuration exception is raised 399 """ 400 for n in Utils.to_list(filename): 401 for d in Utils.to_list(path_list): 402 p = os.path.expanduser(os.path.join(d, n)) 403 if os.path.exists(p): 404 return p 405 self.fatal('Could not find %r' % filename) 406 407@conf 408def find_program(self, filename, **kw): 409 """ 410 Search for a program on the operating system 411 412 When var is used, you may set os.environ[var] to help find a specific program version, for example:: 413 414 $ CC='ccache gcc' waf configure 415 416 :param path_list: paths to use for searching 417 :type param_list: list of string 418 :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 419 :type var: string 420 :param value: obtain the program from the value passed exclusively 421 :type value: list or string (list is preferred) 422 :param exts: list of extensions for the binary (do not add an extension for portability) 423 :type exts: list of string 424 :param msg: name to display in the log, by default filename is used 425 :type msg: string 426 :param interpreter: interpreter for the program 427 :type interpreter: ConfigSet variable key 428 :raises: :py:class:`waflib.Errors.ConfigurationError` 429 """ 430 431 exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py') 432 433 environ = kw.get('environ', getattr(self, 'environ', os.environ)) 434 435 ret = '' 436 437 filename = Utils.to_list(filename) 438 msg = kw.get('msg', ', '.join(filename)) 439 440 var = kw.get('var', '') 441 if not var: 442 var = re.sub(r'[-.]', '_', filename[0].upper()) 443 444 path_list = kw.get('path_list', '') 445 if path_list: 446 path_list = Utils.to_list(path_list) 447 else: 448 path_list = environ.get('PATH', '').split(os.pathsep) 449 450 if kw.get('value'): 451 # user-provided in command-line options and passed to find_program 452 ret = self.cmd_to_list(kw['value']) 453 elif environ.get(var): 454 # user-provided in the os environment 455 ret = self.cmd_to_list(environ[var]) 456 elif self.env[var]: 457 # a default option in the wscript file 458 ret = self.cmd_to_list(self.env[var]) 459 else: 460 if not ret: 461 ret = self.find_binary(filename, exts.split(','), path_list) 462 if not ret and Utils.winreg: 463 ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename) 464 if not ret and Utils.winreg: 465 ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename) 466 ret = self.cmd_to_list(ret) 467 468 if ret: 469 if len(ret) == 1: 470 retmsg = ret[0] 471 else: 472 retmsg = ret 473 else: 474 retmsg = False 475 476 self.msg('Checking for program %r' % msg, retmsg, **kw) 477 if not kw.get('quiet'): 478 self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret)) 479 480 if not ret: 481 self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename) 482 483 interpreter = kw.get('interpreter') 484 if interpreter is None: 485 if not Utils.check_exe(ret[0], env=environ): 486 self.fatal('Program %r is not executable' % ret) 487 self.env[var] = ret 488 else: 489 self.env[var] = self.env[interpreter] + ret 490 491 return ret 492 493@conf 494def find_binary(self, filenames, exts, paths): 495 for f in filenames: 496 for ext in exts: 497 exe_name = f + ext 498 if os.path.isabs(exe_name): 499 if os.path.isfile(exe_name): 500 return exe_name 501 else: 502 for path in paths: 503 x = os.path.expanduser(os.path.join(path, exe_name)) 504 if os.path.isfile(x): 505 return x 506 return None 507 508@conf 509def run_build(self, *k, **kw): 510 """ 511 Create a temporary build context to execute a build. A reference to that build 512 context is kept on self.test_bld for debugging purposes, and you should not rely 513 on it too much (read the note on the cache below). 514 The parameters given in the arguments to this function are passed as arguments for 515 a single task generator created in the build. Only three parameters are obligatory: 516 517 :param features: features to pass to a task generator created in the build 518 :type features: list of string 519 :param compile_filename: file to create for the compilation (default: *test.c*) 520 :type compile_filename: string 521 :param code: code to write in the filename to compile 522 :type code: string 523 524 Though this function returns *0* by default, the build may set an attribute named *retval* on the 525 build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example. 526 527 This function also features a cache which can be enabled by the following option:: 528 529 def options(opt): 530 opt.add_option('--confcache', dest='confcache', default=0, 531 action='count', help='Use a configuration cache') 532 533 And execute the configuration with the following command-line:: 534 535 $ waf configure --confcache 536 537 """ 538 buf = [] 539 for key in sorted(kw.keys()): 540 v = kw[key] 541 if hasattr(v, '__call__'): 542 buf.append(Utils.h_fun(v)) 543 else: 544 buf.append(str(v)) 545 h = Utils.h_list(buf) 546 dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h) 547 548 cachemode = kw.get('confcache', getattr(Options.options, 'confcache', None)) 549 550 if not cachemode and os.path.exists(dir): 551 shutil.rmtree(dir) 552 553 try: 554 os.makedirs(dir) 555 except OSError: 556 pass 557 558 try: 559 os.stat(dir) 560 except OSError: 561 self.fatal('cannot use the configuration test folder %r' % dir) 562 563 if cachemode == 1: 564 try: 565 proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build')) 566 except EnvironmentError: 567 pass 568 else: 569 ret = proj['cache_run_build'] 570 if isinstance(ret, str) and ret.startswith('Test does not build'): 571 self.fatal(ret) 572 return ret 573 574 bdir = os.path.join(dir, 'testbuild') 575 576 if not os.path.exists(bdir): 577 os.makedirs(bdir) 578 579 cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build') 580 self.test_bld = bld = Context.create_context(cls_name, top_dir=dir, out_dir=bdir) 581 bld.init_dirs() 582 bld.progress_bar = 0 583 bld.targets = '*' 584 585 bld.logger = self.logger 586 bld.all_envs.update(self.all_envs) # not really necessary 587 bld.env = kw['env'] 588 589 bld.kw = kw 590 bld.conf = self 591 kw['build_fun'](bld) 592 ret = -1 593 try: 594 try: 595 bld.compile() 596 except Errors.WafError: 597 ret = 'Test does not build: %s' % traceback.format_exc() 598 self.fatal(ret) 599 else: 600 ret = getattr(bld, 'retval', 0) 601 finally: 602 if cachemode: 603 # cache the results each time 604 proj = ConfigSet.ConfigSet() 605 proj['cache_run_build'] = ret 606 proj.store(os.path.join(dir, 'cache_run_build')) 607 else: 608 shutil.rmtree(dir) 609 return ret 610 611@conf 612def ret_msg(self, msg, args): 613 if isinstance(msg, str): 614 return msg 615 return msg(args) 616 617@conf 618def test(self, *k, **kw): 619 620 if not 'env' in kw: 621 kw['env'] = self.env.derive() 622 623 # validate_c for example 624 if kw.get('validate'): 625 kw['validate'](kw) 626 627 self.start_msg(kw['msg'], **kw) 628 ret = None 629 try: 630 ret = self.run_build(*k, **kw) 631 except self.errors.ConfigurationError: 632 self.end_msg(kw['errmsg'], 'YELLOW', **kw) 633 if Logs.verbose > 1: 634 raise 635 else: 636 self.fatal('The configuration failed') 637 else: 638 kw['success'] = ret 639 640 if kw.get('post_check'): 641 ret = kw['post_check'](kw) 642 643 if ret: 644 self.end_msg(kw['errmsg'], 'YELLOW', **kw) 645 self.fatal('The configuration failed %r' % ret) 646 else: 647 self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw) 648 return ret 649 650