3
# Thomas Nagy, 2005-2008 (ita)
8
A configuration instance is created when "waf configure" is called, it is used to:
9
* create data dictionaries (Environment instances)
10
* store the list of modules to import
12
The old model (copied from Scons) was to store logic (mapping file extensions to functions)
13
along with the data. In Waf a way was found to separate that logic by adding an indirection
14
layer (storing the names in the Environment instances)
16
In the new model, the logic is more object-oriented, and the user scripts provide the
17
logic. The data files (Environments) must contain configuration data only (flags, ..).
19
Note: the c/c++ related code is in the module config_c
22
import os, shlex, sys, time
24
except ImportError: import pickle as cPickle
25
import Environment, Utils, Options, Logs
27
from Constants import *
30
from urllib import request
32
from urllib import urlopen
34
urlopen = request.urlopen
36
conf_template = '''# project %(app)s configured on %(now)s by
37
# waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
42
class ConfigurationError(Utils.WscriptError):
46
"reconfigure the project automatically"
48
def find_file(filename, path_list):
49
"""find a file in a list of paths
50
@param filename: name of the file to search for
51
@param path_list: list of directories to search
52
@return: the first occurrence filename or '' if filename could not be found
54
for directory in Utils.to_list(path_list):
55
if os.path.exists(os.path.join(directory, filename)):
59
def find_program_impl(env, filename, path_list=[], var=None, environ=None):
60
"""find a program in folders path_lst, and sets env[var]
61
@param env: environment
62
@param filename: name of the program to search for
63
@param path_list: list of directories to search for filename
64
@param var: environment value to be checked for in env or os.environ
65
@return: either the value that is referenced with [var] in env or os.environ
66
or the first occurrence filename or '' if filename could not be found
72
try: path_list = path_list.split()
73
except AttributeError: pass
76
if env[var]: return env[var]
77
if var in environ: env[var] = environ[var]
79
if not path_list: path_list = environ.get('PATH', '').split(os.pathsep)
81
ext = (Options.platform == 'win32') and '.exe,.com,.bat,.cmd' or ''
82
for y in [filename+x for x in ext.split(',')]:
83
for directory in path_list:
84
x = os.path.join(directory, y)
90
class ConfigurationContext(Utils.Context):
93
def __init__(self, env=None, blddir='', srcdir=''):
97
self.environ = dict(os.environ)
105
# curdir: necessary for recursion
106
self.cwd = self.curdir = os.getcwd()
108
self.tools = [] # tools loaded in the configuration, and that will be loaded when building
124
self.cachedir = os.path.join(self.blddir, CACHE_DIR)
126
path = os.path.join(self.blddir, WAF_CONFIG_LOG)
128
except (OSError, IOError): pass
131
self.log = open(path, 'w')
132
except (OSError, IOError):
133
self.fatal('could not open %r for writing' % path)
135
app = Utils.g_module.APPNAME
137
ver = getattr(Utils.g_module, 'VERSION', '')
139
app = "%s (%s)" % (app, ver)
142
pyver = sys.hexversion
143
systype = sys.platform
144
args = " ".join(sys.argv)
147
self.log.write(conf_template % vars())
150
"""cleanup function: close config.log"""
152
# may be ran by the gc, not always after initialization
153
if hasattr(self, 'log') and self.log:
156
def fatal(self, msg):
157
raise ConfigurationError(msg)
159
def check_tool(self, input, tooldir=None, funs=None):
162
tools = Utils.to_list(input)
163
if tooldir: tooldir = Utils.to_list(tooldir)
165
tool = tool.replace('++', 'xx')
166
if tool == 'java': tool = 'javaw'
167
if tool.lower() == 'unittest': tool = 'unittestw'
168
# avoid loading the same tool more than once with the same functions
169
# used by composite projects
171
mag = (tool, id(self.env), funs)
172
if mag in self.tool_cache:
174
self.tool_cache.append(mag)
178
module = Utils.load_tool(tool, tooldir)
181
if Options.options.download:
182
_3rdparty = os.path.normpath(Options.tooldir[0] + os.sep + '..' + os.sep + '3rdparty')
184
# try to download the tool from the repository then
185
# the default is set to false
186
for x in Utils.to_list(Options.remote_repo):
187
for sub in ['branches/waf-%s/wafadmin/3rdparty' % WAFVERSION, 'trunk/wafadmin/3rdparty']:
188
url = '/'.join((x, sub, tool + '.py'))
191
if web.getcode() != 200:
194
# on python3 urlopen throws an exception
199
loc = open(_3rdparty + os.sep + tool + '.py', 'wb')
200
loc.write(web.read())
205
Logs.warn('downloaded %s from %s' % (tool, url))
207
module = Utils.load_tool(tool, tooldir)
209
Logs.warn('module %s from %s is unusable' % (tool, url))
211
os.unlink(_3rdparty + os.sep + tool + '.py')
219
Logs.error('Could not load the tool %r or download a suitable replacement from the repository (sys.path %r)\n%s' % (tool, sys.path, e))
222
Logs.error('Could not load the tool %r in %r (try the --download option?):\n%s' % (tool, sys.path, e))
226
self.eval_rules(funs)
228
func = getattr(module, 'detect', None)
230
if type(func) is type(find_file): func(self)
231
else: self.eval_rules(func)
233
self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
235
def sub_config(self, k):
236
"executes the configure function of a wscript module"
237
self.recurse(k, name='configure')
239
def pre_recurse(self, name_or_mod, path, nexdir):
240
return {'conf': self, 'ctx': self}
242
def post_recurse(self, name_or_mod, path, nexdir):
245
self.hash = hash((self.hash, getattr(name_or_mod, 'waf_hash_val', name_or_mod)))
246
self.files.append(path)
248
def store(self, file=''):
249
"save the config results into the cache file"
250
if not os.path.isdir(self.cachedir):
251
os.makedirs(self.cachedir)
254
file = open(os.path.join(self.cachedir, 'build.config.py'), 'w')
255
file.write('version = 0x%x\n' % HEXVERSION)
256
file.write('tools = %r\n' % self.tools)
259
if not self.all_envs:
260
self.fatal('nothing to store in the configuration context!')
261
for key in self.all_envs:
262
tmpenv = self.all_envs[key]
263
tmpenv.store(os.path.join(self.cachedir, key + CACHE_SUFFIX))
265
def set_env_name(self, name, env):
266
"add a new environment called name"
267
self.all_envs[name] = env
270
def retrieve(self, name, fromenv=None):
271
"retrieve an environment called name"
273
env = self.all_envs[name]
275
env = Environment.Environment()
276
env['PREFIX'] = os.path.abspath(os.path.expanduser(Options.options.prefix))
277
self.all_envs[name] = env
279
if fromenv: warn("The environment %s may have been configured already" % name)
282
def setenv(self, name):
283
"enable the environment called name"
284
self.env = self.retrieve(name)
287
def add_os_flags(self, var, dest=None):
288
# do not use 'get' to make certain the variable is not defined
289
try: self.env.append_value(dest or var, Utils.to_list(self.environ[var]))
290
except KeyError: pass
292
def check_message_1(self, sr):
293
self.line_just = max(self.line_just, len(sr))
294
for x in ('\n', self.line_just * '-', '\n', sr, '\n'):
296
Utils.pprint('NORMAL', "%s :" % sr.ljust(self.line_just), sep='')
298
def check_message_2(self, sr, color='GREEN'):
301
Utils.pprint(color, sr)
303
def check_message(self, th, msg, state, option=''):
304
sr = 'Checking for %s %s' % (th, msg)
305
self.check_message_1(sr)
306
p = self.check_message_2
307
if state: p('ok ' + str(option))
308
else: p('not found', 'YELLOW')
310
# FIXME remove in waf 1.6
311
# the parameter 'option' is not used (kept for compatibility)
312
def check_message_custom(self, th, msg, custom, option='', color='PINK'):
313
sr = 'Checking for %s %s' % (th, msg)
314
self.check_message_1(sr)
315
self.check_message_2(custom, color)
317
def msg(self, msg, result, color=None):
318
"""Prints a configuration message 'Checking for xxx: ok'"""
319
self.start_msg('Checking for ' + msg)
321
if not isinstance(color, str):
322
color = result and 'GREEN' or 'YELLOW'
324
self.end_msg(result, color)
326
def start_msg(self, msg):
334
self.line_just = max(self.line_just, len(msg))
335
for x in ('\n', self.line_just * '-', '\n', msg, '\n'):
337
Utils.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
339
def end_msg(self, result, color):
348
elif result == False:
356
Utils.pprint(color, msg)
358
def find_program(self, filename, path_list=[], var=None, mandatory=False):
359
"wrapper that adds a configuration message"
365
elif var in os.environ:
366
ret = os.environ[var]
368
if not isinstance(filename, list): filename = [filename]
371
ret = find_program_impl(self.env, x, path_list, var, environ=self.environ)
374
self.check_message_1('Checking for program %s' % ' or '.join(filename))
375
self.log.write(' find program=%r paths=%r var=%r\n -> %r\n' % (filename, path_list, var, ret))
377
Utils.pprint('GREEN', str(ret))
379
Utils.pprint('YELLOW', 'not found')
381
self.fatal('The program %r is required' % filename)
387
def cmd_to_list(self, cmd):
388
"commands may be written in pseudo shell like 'ccache g++'"
389
if isinstance(cmd, str) and cmd.find(' '):
393
return shlex.split(cmd)
398
def __getattr__(self, name):
399
r = self.__class__.__dict__.get(name, None)
401
if name and name.startswith('require_'):
403
for k in ['check_', 'find_']:
404
n = name.replace('require_', k)
405
ret = self.__class__.__dict__.get(n, None)
408
r = ret(self, *k, **kw)
410
self.fatal('requirement failure')
413
self.fatal('No such method %r' % name)
415
def eval_rules(self, rules):
416
self.rules = Utils.to_list(rules)
419
if not f: self.fatal("No such method '%s'." % x)
423
ret = self.err_handler(x, e)
426
elif ret == CONTINUE:
431
def err_handler(self, fun, error):
435
"decorator: attach new configuration functions"
436
setattr(ConfigurationContext, f.__name__, f)
440
"decorator: attach new configuration tests (registered as strings)"
441
ConfigurationContext.tests[f.__name__] = f