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