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 conf
.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
)
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
))
191 def prepare_env(self
, env
):
193 Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
195 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
196 :param env: a ConfigSet, usually ``conf.env``
199 if Options
.options
.prefix
or Utils
.is_win32
:
200 env
.PREFIX
= Options
.options
.prefix
204 if Options
.options
.bindir
:
205 env
.BINDIR
= Options
.options
.bindir
207 env
.BINDIR
= Utils
.subst_vars('${PREFIX}/bin', env
)
209 if Options
.options
.libdir
:
210 env
.LIBDIR
= Options
.options
.libdir
212 env
.LIBDIR
= Utils
.subst_vars('${PREFIX}/lib%s' % Utils
.lib64(), env
)
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
))
219 if not self
.all_envs
:
220 self
.fatal('nothing to store in the configuration context!')
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
))
226 def load(self
, tool_list
, tooldir
=None, funs
=None, with_sys_path
=True, cache
=False):
228 Load Waf tools, which will be imported whenever a build is started.
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
240 tools
= Utils
.to_list(tool_list
)
242 tooldir
= Utils
.to_list(tooldir
)
244 # avoid loading the same tool more than once with the same functions
245 # used by composite projects
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
)
252 self
.tool_cache
.append(mag
)
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())
265 self
.eval_rules(funs
)
267 func
= getattr(module
, 'configure', None)
269 if type(func
) is type(Utils
.readf
):
272 self
.eval_rules(func
)
274 self
.tools
.append({'tool':tool
, 'tooldir':tooldir
, 'funs':funs
})
276 def post_recurse(self
, node
):
278 Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
281 :type node: :py:class:`waflib.Node.Node`
283 super(ConfigurationContext
, self
).post_recurse(node
)
284 self
.hash = Utils
.h_list((self
.hash, node
.read('rb')))
285 self
.files
.append(node
.abspath())
287 def eval_rules(self
, rules
):
289 Execute configuration tests provided as list of functions to run
291 :param rules: list of configuration method names
292 :type rules: list of string
294 self
.rules
= Utils
.to_list(rules
)
298 self
.fatal('No such configuration function %r' % x
)
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::
308 conf.find_program('abc', mandatory=False)
310 :param f: method to bind
314 mandatory
= kw
.pop('mandatory', True)
317 except Errors
.ConfigurationError
:
321 fun
.__name
__ = f
.__name
__
322 setattr(ConfigurationContext
, f
.__name
__, fun
)
323 setattr(Build
.BuildContext
, f
.__name
__, fun
)
327 def add_os_flags(self
, var
, dest
=None, dup
=False):
329 Import operating system environment values into ``conf.env`` dict::
332 conf.add_os_flags('CFLAGS')
334 :param var: variable to use
336 :param dest: destination variable, by default the same as var
338 :param dup: add the same set of flags again
342 flags
= shlex
.split(self
.environ
[var
])
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
)
349 def cmd_to_list(self
, cmd
):
351 Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
354 :type cmd: a string or a list of string
356 if isinstance(cmd
, str):
357 if os
.path
.isfile(cmd
):
358 # do not take any risk
361 return shlex
.split(cmd
)
364 return shlex
.split(cmd
, posix
=False)
366 # Python 2.5 on windows?
367 return shlex
.split(cmd
)
371 def check_waf_version(self
, mini
='1.9.99', maxi
='2.1.0', **kw
):
373 Raise a Configuration error if the Waf version does not strictly match the given bounds::
375 conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
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
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
)
391 def find_file(self
, filename
, path_list
=[]):
393 Find a file in a list of paths
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
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
):
404 self
.fatal('Could not find %r' % filename
)
407 def find_program(self
, filename
, **kw
):
409 Search for a program on the operating system
411 When var is used, you may set os.environ[var] to help find a specific program version, for example::
413 $ CC='ccache gcc' waf configure
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
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
425 :param interpreter: interpreter for the program
426 :type interpreter: ConfigSet variable key
427 :raises: :py:class:`waflib.Errors.ConfigurationError`
430 exts
= kw
.get('exts', Utils
.is_win32
and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
432 environ
= kw
.get('environ', getattr(self
, 'environ', os
.environ
))
436 filename
= Utils
.to_list(filename
)
437 msg
= kw
.get('msg', ', '.join(filename
))
439 var
= kw
.get('var', '')
441 var
= re
.sub(r
'[-.]', '_', filename
[0].upper())
443 path_list
= kw
.get('path_list', '')
445 path_list
= Utils
.to_list(path_list
)
447 path_list
= environ
.get('PATH', '').split(os
.pathsep
)
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
])
456 # a default option in the wscript file
457 ret
= self
.cmd_to_list(self
.env
[var
])
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
)
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
))
480 self
.fatal(kw
.get('errmsg', '') or 'Could not find the program %r' % filename
)
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
)
488 self
.env
[var
] = self
.env
[interpreter
] + ret
493 def find_binary(self
, filenames
, exts
, paths
):
497 if os
.path
.isabs(exe_name
):
498 if os
.path
.isfile(exe_name
):
502 x
= os
.path
.expanduser(os
.path
.join(path
, exe_name
))
503 if os
.path
.isfile(x
):
508 def run_build(self
, *k
, **kw
):
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:
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
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.
526 This function also provides a limited cache. To use it, provide the following option::
529 opt.add_option('--confcache', dest='confcache', default=0,
530 action='count', help='Use a configuration cache')
532 And execute the configuration with the following command-line::
534 $ waf configure --confcache
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
)
549 self
.fatal('cannot use the configuration test folder %r' % dir)
551 cachemode
= getattr(Options
.options
, 'confcache', None)
554 proj
= ConfigSet
.ConfigSet(os
.path
.join(dir, 'cache_run_build'))
555 except EnvironmentError:
558 ret
= proj
['cache_run_build']
559 if isinstance(ret
, str) and ret
.startswith('Test does not build'):
563 bdir
= os
.path
.join(dir, 'testbuild')
565 if not os
.path
.exists(bdir
):
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
)
574 bld
.logger
= self
.logger
575 bld
.all_envs
.update(self
.all_envs
) # not really necessary
585 except Errors
.WafError
:
586 ret
= 'Test does not build: %s' % traceback
.format_exc()
589 ret
= getattr(bld
, 'retval', 0)
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'))
601 def ret_msg(self
, msg
, args
):
602 if isinstance(msg
, str):
607 def test(self
, *k
, **kw
):
610 kw
['env'] = self
.env
.derive()
612 # validate_c for example
613 if kw
.get('validate'):
616 self
.start_msg(kw
['msg'], **kw
)
619 ret
= self
.run_build(*k
, **kw
)
620 except self
.errors
.ConfigurationError
:
621 self
.end_msg(kw
['errmsg'], 'YELLOW', **kw
)
625 self
.fatal('The configuration failed')
629 if kw
.get('post_check'):
630 ret
= kw
['post_check'](kw
)
633 self
.end_msg(kw
['errmsg'], 'YELLOW', **kw
)
634 self
.fatal('The configuration failed %r' % ret
)
636 self
.end_msg(self
.ret_msg(kw
['okmsg'], kw
), **kw
)