1#!/usr/bin/env python
2# coding=utf-8
3# Mathieu Courtois - EDF R&D, 2013 - http://www.code-aster.org
4
5"""
6When a project has a lot of options the 'waf configure' command line can be
7very long and it becomes a cause of error.
8This tool provides a convenient way to load a set of configuration parameters
9from a local file or from a remote url.
10
11The configuration parameters are stored in a Python file that is imported as
12an extra waf tool can be.
13
14Example:
15$ waf configure --use-config-dir=http://www.anywhere.org --use-config=myconf1 ...
16
17The file 'myconf1' will be downloaded from 'http://www.anywhere.org'
18(or 'http://www.anywhere.org/wafcfg').
19If the files are available locally, it could be:
20$ waf configure --use-config-dir=/somewhere/myconfigurations --use-config=myconf1 ...
21
22The configuration of 'myconf1.py' is automatically loaded by calling
23its 'configure' function. In this example, it defines environment variables and
24set options:
25
26def configure(self):
27	self.env['CC'] = 'gcc-4.8'
28	self.env.append_value('LIBPATH', [...])
29	self.options.perlbinary = '/usr/local/bin/perl'
30	self.options.pyc = False
31
32The corresponding command line should have been:
33$ CC=gcc-4.8 LIBPATH=... waf configure --nopyc --with-perl-binary=/usr/local/bin/perl
34
35
36This is an extra tool, not bundled with the default waf binary.
37To add the use_config tool to the waf file:
38$ ./waf-light --tools=use_config
39
40When using this tool, the wscript will look like:
41
42	def options(opt):
43		opt.load('use_config')
44
45	def configure(conf):
46		conf.load('use_config')
47"""
48
49import sys
50import os.path as osp
51import os
52
53local_repo = ''
54"""Local repository containing additional Waf tools (plugins)"""
55remote_repo = 'https://gitlab.com/ita1024/waf/raw/master/'
56"""
57Remote directory containing downloadable waf tools. The missing tools can be downloaded by using::
58
59	$ waf configure --download
60"""
61
62remote_locs = ['waflib/extras', 'waflib/Tools']
63"""
64Remote directories for use with :py:const:`waflib.extras.use_config.remote_repo`
65"""
66
67
68try:
69	from urllib import request
70except ImportError:
71	from urllib import urlopen
72else:
73	urlopen = request.urlopen
74
75
76from waflib import Errors, Context, Logs, Utils, Options, Configure
77
78try:
79	from urllib.parse import urlparse
80except ImportError:
81	from urlparse import urlparse
82
83
84
85
86DEFAULT_DIR = 'wafcfg'
87# add first the current wafcfg subdirectory
88sys.path.append(osp.abspath(DEFAULT_DIR))
89
90def options(self):
91	group = self.add_option_group('configure options')
92	group.add_option('--download', dest='download', default=False, action='store_true', help='try to download the tools if missing')
93
94	group.add_option('--use-config', action='store', default=None,
95					 metavar='CFG', dest='use_config',
96					 help='force the configuration parameters by importing '
97						  'CFG.py. Several modules may be provided (comma '
98						  'separated).')
99	group.add_option('--use-config-dir', action='store', default=DEFAULT_DIR,
100					 metavar='CFG_DIR', dest='use_config_dir',
101					 help='path or url where to find the configuration file')
102
103def download_check(node):
104	"""
105	Hook to check for the tools which are downloaded. Replace with your function if necessary.
106	"""
107	pass
108
109
110def download_tool(tool, force=False, ctx=None):
111	"""
112	Download a Waf tool from the remote repository defined in :py:const:`waflib.extras.use_config.remote_repo`::
113
114		$ waf configure --download
115	"""
116	for x in Utils.to_list(remote_repo):
117		for sub in Utils.to_list(remote_locs):
118			url = '/'.join((x, sub, tool + '.py'))
119			try:
120				web = urlopen(url)
121				try:
122					if web.getcode() != 200:
123						continue
124				except AttributeError:
125					pass
126			except Exception:
127				# on python3 urlopen throws an exception
128				# python 2.3 does not have getcode and throws an exception to fail
129				continue
130			else:
131				tmp = ctx.root.make_node(os.sep.join((Context.waf_dir, 'waflib', 'extras', tool + '.py')))
132				tmp.write(web.read(), 'wb')
133				Logs.warn('Downloaded %s from %s', tool, url)
134				download_check(tmp)
135				try:
136					module = Context.load_tool(tool)
137				except Exception:
138					Logs.warn('The tool %s from %s is unusable', tool, url)
139					try:
140						tmp.delete()
141					except Exception:
142						pass
143					continue
144				return module
145
146	raise Errors.WafError('Could not load the Waf tool')
147
148def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
149	try:
150		module = Context.load_tool_default(tool, tooldir, ctx, with_sys_path)
151	except ImportError as e:
152		if not ctx or not hasattr(Options.options, 'download'):
153			Logs.error('Could not load %r during options phase (download unavailable at this point)' % tool)
154			raise
155		if Options.options.download:
156			module = download_tool(tool, ctx=ctx)
157			if not module:
158				ctx.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))
159		else:
160			ctx.fatal('Could not load the Waf tool %r from %r (try the --download option?):\n%s' % (tool, sys.path, e))
161	return module
162
163Context.load_tool_default = Context.load_tool
164Context.load_tool = load_tool
165Configure.download_tool = download_tool
166
167def configure(self):
168	opts = self.options
169	use_cfg = opts.use_config
170	if use_cfg is None:
171		return
172	url = urlparse(opts.use_config_dir)
173	kwargs = {}
174	if url.scheme:
175		kwargs['download'] = True
176		kwargs['remote_url'] = url.geturl()
177		# search first with the exact url, else try with +'/wafcfg'
178		kwargs['remote_locs'] = ['', DEFAULT_DIR]
179	tooldir = url.geturl() + ' ' + DEFAULT_DIR
180	for cfg in use_cfg.split(','):
181		Logs.pprint('NORMAL', "Searching configuration '%s'..." % cfg)
182		self.load(cfg, tooldir=tooldir, **kwargs)
183	self.start_msg('Checking for configuration')
184	self.end_msg(use_cfg)
185
186