3 # Thomas Nagy, 2005-2018 (ita)
8 A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``waf configure`` is called, it is used to:
10 * create data dictionaries (ConfigSet instances)
11 * store the list of modules to import
12 * hold configuration routines such as ``find_program``, etc
15 import os
, re
, shlex
, shutil
, sys
, time
, traceback
16 from waflib
import ConfigSet
, Utils
, Options
, Logs
, Context
, Build
, Errors
18 WAF_CONFIG_LOG
= 'config.log'
19 """Name of the configuration log file"""
22 """Execute the configuration automatically"""
24 conf_template
= '''# project %(app)s configured on %(now)s by
25 # waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
29 class ConfigurationContext(Context
.Context
):
30 '''configures the project'''
36 Additional functions to handle configuration errors
39 def __init__(self
, **kw
):
40 super(ConfigurationContext
, self
).__init
__(**kw
)
41 self
.environ
= dict(os
.environ
)
47 self
.tools
= [] # tools loaded in the configuration, and that will be loaded when building
56 def setenv(self
, name
, env
=None):
58 Set a new config set for conf.env. If a config set of that name already exists,
59 recall it without modification.
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::
71 2 == bld.env_of_name('foo').ONE
73 :param name: name of the configuration set
75 :param env: ConfigSet to copy, or an empty ConfigSet is created
76 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
78 if name
not in self
.all_envs
or env
:
80 env
= ConfigSet
.ConfigSet()
84 self
.all_envs
[name
] = env
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
94 env
= property(get_env
, set_env
)
98 Initialize the project directory and the build directory
103 top
= Options
.options
.top
105 top
= getattr(Context
.g_module
, Context
.TOP
, None)
107 top
= self
.path
.abspath()
108 top
= os
.path
.abspath(top
)
110 self
.srcnode
= (os
.path
.isabs(top
) and self
.root
or self
.path
).find_dir(top
)
115 out
= Options
.options
.out
117 out
= getattr(Context
.g_module
, Context
.OUT
, None)
119 out
= Options
.lockfile
.replace('.lock-waf_%s_' % sys
.platform
, '').replace('.lock-waf', '')
121 # someone can be messing with symlinks
122 out
= os
.path
.realpath(out
)
124 self
.bldnode
= (os
.path
.isabs(out
) and self
.root
or self
.path
).make_node(out
)
127 if not os
.path
.isdir(self
.bldnode
.abspath()):
128 self
.fatal('Could not create the build directory %s' % self
.bldnode
.abspath())
132 See :py:func:`waflib.Context.Context.execute`
136 self
.cachedir
= self
.bldnode
.make_node(Build
.CACHE_DIR
)
137 self
.cachedir
.mkdir()
139 path
= os
.path
.join(self
.bldnode
.abspath(), WAF_CONFIG_LOG
)
140 self
.logger
= Logs
.make_logger(path
, 'cfg')
142 app
= getattr(Context
.g_module
, 'APPNAME', '')
144 ver
= getattr(Context
.g_module
, 'VERSION', '')
146 app
= "%s (%s)" % (app
, ver
)
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())
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="." ?')
159 super(ConfigurationContext
, self
).execute()
163 Context
.top_dir
= self
.srcnode
.abspath()
164 Context
.out_dir
= self
.bldnode
.abspath()
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()
171 env
.options
= Options
.options
.__dict
__
172 env
.config_cmd
= self
.cmd
174 env
.run_dir
= Context
.run_dir
175 env
.top_dir
= Context
.top_dir
176 env
.out_dir
= Context
.out_dir
178 # conf.hash & conf.files hold wscript files paths and hash
179 # (used only by Configure.autoconfig)
181 env
.files
= self
.files
182 env
.environ
= dict(self
.environ
)
183 env
.launch_dir
= Context
.launch_dir
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
))
192 def prepare_env(self
, env
):
194 Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
196 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
197 :param env: a ConfigSet, usually ``conf.env``
200 if Options
.options
.prefix
or Utils
.is_win32
:
201 env
.PREFIX
= Options
.options
.prefix
205 if Options
.options
.bindir
:
206 env
.BINDIR
= Options
.options
.bindir
208 env
.BINDIR
= Utils
.subst_vars('${PREFIX}/bin', env
)
210 if Options
.options
.libdir
:
211 env
.LIBDIR
= Options
.options
.libdir
213 env
.LIBDIR
= Utils
.subst_vars('${PREFIX}/lib%s' % Utils
.lib64(), env
)
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
))
220 if not self
.all_envs
:
221 self
.fatal('nothing to store in the configuration context!')
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
))
227 def load(self
, tool_list
, tooldir
=None, funs
=None, with_sys_path
=True, cache
=False):
229 Load Waf tools, which will be imported whenever a build is started.
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
241 tools
= Utils
.to_list(tool_list
)
243 tooldir
= Utils
.to_list(tooldir
)
245 # avoid loading the same tool more than once with the same functions
246 # used by composite projects
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
)
253 self
.tool_cache
.append(mag
)
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())
266 self
.eval_rules(funs
)
268 func
= getattr(module
, 'configure', None)
270 if type(func
) is type(Utils
.readf
):
273 self
.eval_rules(func
)
275 self
.tools
.append({'tool':tool
, 'tooldir':tooldir
, 'funs':funs
})
277 def post_recurse(self
, node
):
279 Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
282 :type node: :py:class:`waflib.Node.Node`
284 super(ConfigurationContext
, self
).post_recurse(node
)
285 self
.hash = Utils
.h_list((self
.hash, node
.read('rb')))
286 self
.files
.append(node
.abspath())
288 def eval_rules(self
, rules
):
290 Execute configuration tests provided as list of functions to run
292 :param rules: list of configuration method names
293 :type rules: list of string
295 self
.rules
= Utils
.to_list(rules
)
299 self
.fatal('No such configuration function %r' % x
)
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::
309 conf.find_program('abc', mandatory=False)
311 :param f: method to bind
315 mandatory
= kw
.pop('mandatory', True)
318 except Errors
.ConfigurationError
:
322 fun
.__name
__ = f
.__name
__
323 setattr(ConfigurationContext
, f
.__name
__, fun
)
324 setattr(Build
.BuildContext
, f
.__name
__, fun
)
328 def add_os_flags(self
, var
, dest
=None, dup
=False):
330 Import operating system environment values into ``conf.env`` dict::
333 conf.add_os_flags('CFLAGS')
335 :param var: variable to use
337 :param dest: destination variable, by default the same as var
339 :param dup: add the same set of flags again
343 flags
= shlex
.split(self
.environ
[var
])
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
)
350 def cmd_to_list(self
, cmd
):
352 Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
355 :type cmd: a string or a list of string
357 if isinstance(cmd
, str):
358 if os
.path
.isfile(cmd
):
359 # do not take any risk
362 return shlex
.split(cmd
)
365 return shlex
.split(cmd
, posix
=False)
367 # Python 2.5 on windows?
368 return shlex
.split(cmd
)
372 def check_waf_version(self
, mini
='1.9.99', maxi
='2.1.0', **kw
):
374 Raise a Configuration error if the Waf version does not strictly match the given bounds::
376 conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
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
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
)
392 def find_file(self
, filename
, path_list
=[]):
394 Find a file in a list of paths
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
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
):
405 self
.fatal('Could not find %r' % filename
)
408 def find_program(self
, filename
, **kw
):
410 Search for a program on the operating system
412 When var is used, you may set os.environ[var] to help find a specific program version, for example::
414 $ CC='ccache gcc' waf configure
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
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
426 :param interpreter: interpreter for the program
427 :type interpreter: ConfigSet variable key
428 :raises: :py:class:`waflib.Errors.ConfigurationError`
431 exts
= kw
.get('exts', Utils
.is_win32
and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
433 environ
= kw
.get('environ', getattr(self
, 'environ', os
.environ
))
437 filename
= Utils
.to_list(filename
)
438 msg
= kw
.get('msg', ', '.join(filename
))
440 var
= kw
.get('var', '')
442 var
= re
.sub(r
'\W', '_', filename
[0].upper())
444 path_list
= kw
.get('path_list', '')
446 path_list
= Utils
.to_list(path_list
)
448 path_list
= environ
.get('PATH', '').split(os
.pathsep
)
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
])
457 # a default option in the wscript file
458 ret
= self
.cmd_to_list(self
.env
[var
])
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
)
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
))
481 self
.fatal(kw
.get('errmsg', '') or 'Could not find the program %r' % filename
)
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
)
489 self
.env
[var
] = self
.env
[interpreter
] + ret
494 def find_binary(self
, filenames
, exts
, paths
):
498 if os
.path
.isabs(exe_name
):
499 if os
.path
.isfile(exe_name
):
503 x
= os
.path
.expanduser(os
.path
.join(path
, exe_name
))
504 if os
.path
.isfile(x
):
509 def run_build(self
, *k
, **kw
):
511 Create a temporary build context to execute a build. A temporary reference to that build
512 context is kept on self.test_bld for debugging purposes.
513 The arguments to this function are passed to a single task generator for that build.
514 Only three parameters are mandatory:
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: input file contents
523 Though this function returns *0* by default, the build may bind 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.
526 The temporary builds creates a temporary folder; the name of that folder is calculated
527 by hashing input arguments to this function, with the exception of :py:class:`waflib.ConfigSet.ConfigSet`
528 objects which are used for both reading and writing values.
530 This function also features a cache which is disabled by default; that cache relies
531 on the hash value calculated as indicated above::
534 opt.add_option('--confcache', dest='confcache', default=0,
535 action='count', help='Use a configuration cache')
537 And execute the configuration with the following command-line::
539 $ waf configure --confcache
543 for key
in sorted(kw
.keys()):
545 if isinstance(v
, ConfigSet
.ConfigSet
):
546 # values are being written to, so they are excluded from contributing to the hash
548 elif hasattr(v
, '__call__'):
549 buf
.append(Utils
.h_fun(v
))
552 h
= Utils
.h_list(buf
)
553 dir = self
.bldnode
.abspath() + os
.sep
+ (not Utils
.is_win32
and '.' or '') + 'conf_check_' + Utils
.to_hex(h
)
555 cachemode
= kw
.get('confcache', getattr(Options
.options
, 'confcache', None))
557 if not cachemode
and os
.path
.exists(dir):
568 self
.fatal('cannot use the configuration test folder %r' % dir)
572 proj
= ConfigSet
.ConfigSet(os
.path
.join(dir, 'cache_run_build'))
573 except EnvironmentError:
576 ret
= proj
['cache_run_build']
577 if isinstance(ret
, str) and ret
.startswith('Test does not build'):
581 bdir
= os
.path
.join(dir, 'testbuild')
583 if not os
.path
.exists(bdir
):
586 cls_name
= kw
.get('run_build_cls') or getattr(self
, 'run_build_cls', 'build')
587 self
.test_bld
= bld
= Context
.create_context(cls_name
, top_dir
=dir, out_dir
=bdir
)
592 bld
.logger
= self
.logger
593 bld
.all_envs
.update(self
.all_envs
) # not really necessary
603 except Errors
.WafError
:
604 ret
= 'Test does not build: %s' % traceback
.format_exc()
607 ret
= getattr(bld
, 'retval', 0)
610 # cache the results each time
611 proj
= ConfigSet
.ConfigSet()
612 proj
['cache_run_build'] = ret
613 proj
.store(os
.path
.join(dir, 'cache_run_build'))
619 def ret_msg(self
, msg
, args
):
620 if isinstance(msg
, str):
625 def test(self
, *k
, **kw
):
628 kw
['env'] = self
.env
.derive()
630 # validate_c for example
631 if kw
.get('validate'):
634 self
.start_msg(kw
['msg'], **kw
)
637 ret
= self
.run_build(*k
, **kw
)
638 except self
.errors
.ConfigurationError
:
639 self
.end_msg(kw
['errmsg'], 'YELLOW', **kw
)
643 self
.fatal('The configuration failed')
647 if kw
.get('post_check'):
648 ret
= kw
['post_check'](kw
)
651 self
.end_msg(kw
['errmsg'], 'YELLOW', **kw
)
652 self
.fatal('The configuration failed %r' % ret
)
654 self
.end_msg(self
.ret_msg(kw
['okmsg'], kw
), **kw
)