Source

wxMupen64Plus / waflib / Configure.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
#!/usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005-2010 (ita)

"""
Configuration system

A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``waf configure`` is called, it is used to:

* create data dictionaries (ConfigSet instances)
* store the list of modules to import
* hold configuration routines such as ``find_program``, etc
"""

import os, shlex, sys, time
from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors

try:
	from urllib import request
except:
	from urllib import urlopen
else:
	urlopen = request.urlopen

BREAK    = 'break'
"""In case of a configuration error, break"""

CONTINUE = 'continue'
"""In case of a configuration error, continue"""

WAF_CONFIG_LOG = 'config.log'
"""Name of the configuration log file"""

autoconfig = False
"""Execute the configuration automatically"""

conf_template = '''# project %(app)s configured on %(now)s by
# waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
# using %(args)s
#'''

def download_check(node):
	"""
	Hook to check for the tools which are downloaded. Replace with your function if necessary.
	"""
	pass

def download_tool(tool, force=False, ctx=None):
	"""
	Download a Waf tool from the remote repository defined in :py:const:`waflib.Context.remote_repo`::

		$ waf configure --download
	"""
	for x in Utils.to_list(Context.remote_repo):
		for sub in Utils.to_list(Context.remote_locs):
			url = '/'.join((x, sub, tool + '.py'))
			try:
				web = urlopen(url)
				try:
					if web.getcode() != 200:
						continue
				except AttributeError:
					pass
			except Exception as e:
				# on python3 urlopen throws an exception
				# python 2.3 does not have getcode and throws an exception to fail
				continue
			else:
				tmp = ctx.root.make_node(os.sep.join((Context.waf_dir, 'waflib', 'extras', tool + '.py')))
				tmp.write(web.read())
				Logs.warn('Downloaded %s from %s' % (tool, url))
				download_check(tmp)
				try:
					module = Context.load_tool(tool)
				except:
					Logs.warn('The tool %s from %s is unusable' % (tool, url))
					try:
						tmp.delete()
					except:
						pass
					continue
				return module
	raise Errors.WafError('Could not load the Waf tool')

class ConfigurationContext(Context.Context):
	'''configures the project'''

	cmd = 'configure'

	error_handlers = []
	"""
	Additional functions to handle configuration errors
	"""

	def __init__(self, **kw):
		super(ConfigurationContext, self).__init__(**kw)
		self.environ = dict(os.environ)
		self.all_envs = {}

		self.top_dir = None
		self.out_dir = None

		self.tools = [] # tools loaded in the configuration, and that will be loaded when building

		self.hash = 0
		self.files = []

		self.tool_cache = []

		self.setenv('')

	def setenv(self, name, env=None):
		"""
		Set a new config set for conf.env

		The name is the filename prefix to save to ``c4che/NAME_cache.py``, and it
		is also used as *variants* by the build commands.
		Though related to variants, whatever kind of data may be stored in the config set::

			def configure(cfg):
				cfg.env.ONE = 1
				cfg.setenv('foo')
				cfg.env.ONE = 2

			def build(bld):
				2 == bld.env_of_name('foo').ONE

		:param name: name of the configuration set
		:type name: string
		:param env: ConfigSet to copy, or an empty ConfigSet is created
		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
		"""
		if not env:
			env = ConfigSet.ConfigSet()
			self.prepare_env(env)
		else:
			env = env.derive()
		self.all_envs[name] = env
		self.variant = name

	def get_env(self):
		"""Getter for the env property"""
		return self.all_envs[self.variant]
	def set_env(self, val):
		"""Setter for the env property"""
		self.all_envs[self.variant] = val

	env = property(get_env, set_env)

	def init_dirs(self):
		"""
		Initialize the project directory and the build directory
		"""

		top = self.top_dir
		if not top:
			top = Options.options.top
		if not top:
			top = getattr(Context.g_module, Context.TOP, None)
		if not top:
			top = self.path.abspath()
		top = os.path.abspath(top)

		self.srcnode = (os.path.isabs(top) and self.root or self.path).find_dir(top)
		assert(self.srcnode)

		out = self.out_dir
		if not out:
			out = Options.options.out
		if not out:
			out = getattr(Context.g_module, Context.OUT, None)
		if not out:
			out = Options.lockfile.replace('.lock-waf', '')

		self.bldnode = (os.path.isabs(out) and self.root or self.path).make_node(out)
		self.bldnode.mkdir()

		if not os.path.isdir(self.bldnode.abspath()):
			conf.fatal('could not create the build directory %s' % self.bldnode.abspath())

	def execute(self):
		"""
		See :py:func:`waflib.Context.Context.execute`
		"""
		self.init_dirs()

		self.cachedir = self.bldnode.make_node(Build.CACHE_DIR)
		self.cachedir.mkdir()

		path = os.path.join(self.bldnode.abspath(), WAF_CONFIG_LOG)
		self.logger = Logs.make_logger(path, 'cfg')

		app = getattr(Context.g_module, 'APPNAME', '')
		if app:
			ver = getattr(Context.g_module, 'VERSION', '')
			if ver:
				app = "%s (%s)" % (app, ver)

		now = time.ctime()
		pyver = sys.hexversion
		systype = sys.platform
		args = " ".join(sys.argv)
		wafver = Context.WAFVERSION
		abi = Context.ABI
		self.to_log(conf_template % vars())

		self.msg('Setting top to', self.srcnode.abspath())
		self.msg('Setting out to', self.bldnode.abspath())

		if id(self.srcnode) == id(self.bldnode):
			Logs.warn('Setting top == out (remember to use "update_outputs")')
		elif id(self.path) != id(self.srcnode):
			if self.srcnode.is_child_of(self.path):
				Logs.warn('Are you certain that you do not want to set top="." ?')

		super(ConfigurationContext, self).execute()

		self.store()

		Context.top_dir = self.srcnode.abspath()
		Context.out_dir = self.bldnode.abspath()

		# this will write a configure lock so that subsequent builds will
		# consider the current path as the root directory (see prepare_impl).
		# to remove: use 'waf distclean'
		env = ConfigSet.ConfigSet()
		env['argv'] = sys.argv
		env['options'] = Options.options.__dict__

		env.run_dir = Context.run_dir
		env.top_dir = Context.top_dir
		env.out_dir = Context.out_dir

		# conf.hash & conf.files hold wscript files paths and hash
		# (used only by Configure.autoconfig)
		env['hash'] = self.hash
		env['files'] = self.files
		env['environ'] = dict(self.environ)

		if not self.env.NO_LOCK_IN_RUN:
			env.store(Context.run_dir + os.sep + Options.lockfile)
		if not self.env.NO_LOCK_IN_TOP:
			env.store(Context.top_dir + os.sep + Options.lockfile)
		if not self.env.NO_LOCK_IN_OUT:
			env.store(Context.out_dir + os.sep + Options.lockfile)

	def prepare_env(self, env):
		"""
		Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``

		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
		:param env: a ConfigSet, usually ``conf.env``
		"""
		if not env.PREFIX:
			env.PREFIX = os.path.abspath(os.path.expanduser(Options.options.prefix))
		if not env.BINDIR:
			env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
		if not env.LIBDIR:
			env.LIBDIR = Utils.subst_vars('${PREFIX}/lib', env)

	def store(self):
		"""Save the config results into the cache file"""
		n = self.cachedir.make_node('build.config.py')
		n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools))

		if not self.all_envs:
			self.fatal('nothing to store in the configuration context!')

		for key in self.all_envs:
			tmpenv = self.all_envs[key]
			tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))

	def load(self, input, tooldir=None, funs=None, download=True):
		"""
		Load Waf tools, which will be imported whenever a build is started.

		:param input: waf tools to import
		:type input: list of string
		:param tooldir: paths for the imports
		:type tooldir: list of string
		:param funs: functions to execute from the waf tools
		:type funs: list of string
		:param download: whether to download the tool from the waf repository
		:type download: bool
		"""

		tools = Utils.to_list(input)
		if tooldir: tooldir = Utils.to_list(tooldir)
		for tool in tools:
			# avoid loading the same tool more than once with the same functions
			# used by composite projects

			mag = (tool, id(self.env), funs)
			if mag in self.tool_cache:
				self.to_log('(tool %s is already loaded, skipping)' % tool)
				continue
			self.tool_cache.append(mag)

			module = None
			try:
				module = Context.load_tool(tool, tooldir)
			except ImportError as e:
				if Options.options.download:
					module = download_tool(tool, ctx=self)
					if not module:
						self.fatal('Could not load the Waf tool %r or download a suitable replacement from the repository (sys.path %r)\n%s' % (tool, sys.path, e))
				else:
					self.fatal('Could not load the Waf tool %r from %r (try the --download option?):\n%s' % (tool, sys.path, e))
			except Exception as e:
				self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
				self.to_log(Utils.ex_stack())
				raise

			if funs is not None:
				self.eval_rules(funs)
			else:
				func = getattr(module, 'configure', None)
				if func:
					if type(func) is type(Utils.readf): func(self)
					else: self.eval_rules(func)

			self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})

	def post_recurse(self, node):
		"""
		Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`

		:param node: script
		:type node: :py:class:`waflib.Node.Node`
		"""
		super(ConfigurationContext, self).post_recurse(node)
		self.hash = hash((self.hash, node.read('rb')))
		self.files.append(node.abspath())

	def eval_rules(self, rules):
		"""
		Execute the configuration tests. The method :py:meth:`waflib.Configure.ConfigurationContext.err_handler`
		is used to process the eventual exceptions

		:param rules: list of configuration method names
		:type rules: list of string
		"""
		self.rules = Utils.to_list(rules)
		for x in self.rules:
			f = getattr(self, x)
			if not f: self.fatal("No such method '%s'." % x)
			try:
				f()
			except Exception as e:
				ret = self.err_handler(x, e)
				if ret == BREAK:
					break
				elif ret == CONTINUE:
					continue
				else:
					raise

	def err_handler(self, fun, error):
		"""
		Error handler for the configuration tests, the default is to let the exception raise

		:param fun: configuration test
		:type fun: method
		:param error: exception
		:type error: exception
		"""
		pass

def conf(f):
	"""
	Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and
	:py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter
	named 'mandatory' to disable the configuration errors::

		def configure(conf):
			conf.find_program('abc', mandatory=False)

	:param f: method to bind
	:type f: function
	"""
	def fun(*k, **kw):
		mandatory = True
		if 'mandatory' in kw:
			mandatory = kw['mandatory']
			del kw['mandatory']

		try:
			return f(*k, **kw)
		except Errors.ConfigurationError as e:
			if mandatory:
				raise e

	setattr(ConfigurationContext, f.__name__, fun)
	setattr(Build.BuildContext, f.__name__, fun)
	return f

@conf
def add_os_flags(self, var, dest=None):
	"""
	Import operating system environment values into ``conf.env`` dict::

		def configure(conf):
			conf.add_os_flags('CFLAGS')

	:param var: variable to use
	:type var: string
	:param dest: destination variable, by default the same as var
	:type dest: string
	"""
	# do not use 'get' to make certain the variable is not defined
	try: self.env.append_value(dest or var, shlex.split(self.environ[var]))
	except KeyError: pass

@conf
def cmd_to_list(self, cmd):
	"""
	Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.

	:param cmd: command
	:type cmd: a string or a list of string
	"""
	if isinstance(cmd, str) and cmd.find(' '):
		try:
			os.stat(cmd)
		except OSError:
			return shlex.split(cmd)
		else:
			return [cmd]
	return cmd

@conf
def check_waf_version(self, mini='1.6.0', maxi='1.7.0'):
	"""
	check for the waf version

	Versions should be supplied as hex. 0x01000000 means 1.0.0,
	0x010408 means 1.4.8, etc.

	:type  mini: number, tuple or string
	:param mini: Minimum required version
	:type  maxi: number, tuple or string
	:param maxi: Maximum allowed version
	"""
	self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)))
	ver = Context.HEXVERSION
	if Utils.num2ver(mini) > ver:
		self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver))

	if Utils.num2ver(maxi) < ver:
		self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver))
	self.end_msg('ok')

@conf
def find_file(self, filename, path_list=[]):
	"""
	Find a file in a list of paths

	:param filename: name of the file to search for
	:param path_list: list of directories to search
	:return: the first occurrence filename or '' if filename could not be found
	"""
	for n in Utils.to_list(filename):
		for d in Utils.to_list(path_list):
			p = os.path.join(d, n)
			if os.path.exists(p):
				return p
	self.fatal('Could not find %r' % filename)

@conf
def find_program(self, filename, **kw):
	"""
	Search for a program on the operating system

	When var is used, you may set os.environ[var] to help finding a specific program version, for example::

		$ VALAC=/usr/bin/valac_test waf configure

	:param path_list: paths to use for searching
	:type param_list: list of string
	:param var: store the result to conf.env[var], by default use filename.upper()
	:type var: string
	:param ext: list of extensions for the binary (do not add an extension for portability)
	:type ext: list of string
	"""

	exts = kw.get('exts', Options.platform == 'win32' and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')

	environ = kw.get('environ', os.environ)

	ret = ''
	filename = Utils.to_list(filename)

	var = kw.get('var', '')
	if not var:
		var = filename[0].upper()

	if self.env[var]:
		ret = self.env[var]
	elif var in environ:
		ret = environ[var]

	path_list = kw.get('path_list', '')
	if not ret:
		if path_list:
			path_list = Utils.to_list(path_list)
		else:
			path_list = environ.get('PATH', '').split(os.pathsep)

		if not isinstance(filename, list):
			filename = [filename]

		for a in exts.split(','):
			if ret:
				break
			for b in filename:
				if ret:
					break
				for c in path_list:
					if ret:
						break
					x = os.path.expanduser(os.path.join(c, b + a))
					if os.path.isfile(x):
						ret = x

	if not ret and Utils.winreg:
		ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename)
	if not ret and Utils.winreg:
		ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename)

	self.msg('Checking for program ' + ','.join(filename), ret or False)
	self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret))

	if not ret:
		self.fatal(kw.get('errmsg', '') or 'Could not find the program %s' % ','.join(filename))

	if var:
		self.env[var] = ret
	return ret


@conf
def find_perl_program(self, filename, path_list=[], var=None, environ=None, exts=''):
	"""
	Search for a perl program on the operating system

	:param filename: file to search for
	:type filename: string
	:param path_list: list of paths to look into
	:type path_list: list of string
	:param var: store the results into *conf.env.var*
	:type var: string
	:param environ: operating system environment to pass to :py:func:`waflib.Configure.find_program`
	:type environ: dict
	:param exts: extensions given to :py:func:`waflib.Configure.find_program`
	:type exts: list
	"""

	try:
		app = self.find_program(filename, path_list=path_list, var=var, environ=environ, exts=exts)
	except:
		perl = self.find_program('perl', var='PERL')
		app = self.find_file(filename, os.environ['PATH'].split(os.pathsep))
		if not app:
			raise
		if var:
			self.env[var] = Utils.to_list(self.env['PERL']) + [app]
	self.msg('Checking for %r' % filename, app)