1#! /usr/bin/env python
2# encoding: utf-8
3# DC 2008
4# Thomas Nagy 2016-2018 (ita)
5
6import os, re, traceback
7from waflib import Utils, Logs, Errors
8from waflib.Tools import fc, fc_config, fc_scan, ar, ccroot
9from waflib.Configure import conf
10from waflib.TaskGen import after_method, feature
11
12@conf
13def find_ifort(conf):
14	fc = conf.find_program('ifort', var='FC')
15	conf.get_ifort_version(fc)
16	conf.env.FC_NAME = 'IFORT'
17
18@conf
19def ifort_modifier_win32(self):
20	v = self.env
21	v.IFORT_WIN32 = True
22	v.FCSTLIB_MARKER = ''
23	v.FCSHLIB_MARKER = ''
24
25	v.FCLIB_ST = v.FCSTLIB_ST = '%s.lib'
26	v.FCLIBPATH_ST = v.STLIBPATH_ST = '/LIBPATH:%s'
27	v.FCINCPATH_ST = '/I%s'
28	v.FCDEFINES_ST = '/D%s'
29
30	v.fcprogram_PATTERN = v.fcprogram_test_PATTERN = '%s.exe'
31	v.fcshlib_PATTERN = '%s.dll'
32	v.fcstlib_PATTERN = v.implib_PATTERN = '%s.lib'
33
34	v.FCLNK_TGT_F = '/out:'
35	v.FC_TGT_F = ['/c', '/o', '']
36	v.FCFLAGS_fcshlib = ''
37	v.LINKFLAGS_fcshlib = '/DLL'
38	v.AR_TGT_F = '/out:'
39	v.IMPLIB_ST = '/IMPLIB:%s'
40
41	v.append_value('LINKFLAGS', '/subsystem:console')
42	if v.IFORT_MANIFEST:
43		v.append_value('LINKFLAGS', ['/MANIFEST'])
44
45@conf
46def ifort_modifier_darwin(conf):
47	fc_config.fortran_modifier_darwin(conf)
48
49@conf
50def ifort_modifier_platform(conf):
51	dest_os = conf.env.DEST_OS or Utils.unversioned_sys_platform()
52	ifort_modifier_func = getattr(conf, 'ifort_modifier_' + dest_os, None)
53	if ifort_modifier_func:
54		ifort_modifier_func()
55
56@conf
57def get_ifort_version(conf, fc):
58	"""
59	Detects the compiler version and sets ``conf.env.FC_VERSION``
60	"""
61	version_re = re.compile(r"\bIntel\b.*\bVersion\s*(?P<major>\d*)\.(?P<minor>\d*)",re.I).search
62	if Utils.is_win32:
63		cmd = fc
64	else:
65		cmd = fc + ['-logo']
66
67	out, err = fc_config.getoutput(conf, cmd, stdin=False)
68	match = version_re(out) or version_re(err)
69	if not match:
70		conf.fatal('cannot determine ifort version.')
71	k = match.groupdict()
72	conf.env.FC_VERSION = (k['major'], k['minor'])
73
74def configure(conf):
75	"""
76	Detects the Intel Fortran compilers
77	"""
78	if Utils.is_win32:
79		compiler, version, path, includes, libdirs, arch = conf.detect_ifort()
80		v = conf.env
81		v.DEST_CPU = arch
82		v.PATH = path
83		v.INCLUDES = includes
84		v.LIBPATH = libdirs
85		v.MSVC_COMPILER = compiler
86		try:
87			v.MSVC_VERSION = float(version)
88		except ValueError:
89			v.MSVC_VERSION = float(version[:-3])
90
91		conf.find_ifort_win32()
92		conf.ifort_modifier_win32()
93	else:
94		conf.find_ifort()
95		conf.find_program('xiar', var='AR')
96		conf.find_ar()
97		conf.fc_flags()
98		conf.fc_add_flags()
99		conf.ifort_modifier_platform()
100
101
102all_ifort_platforms = [ ('intel64', 'amd64'), ('em64t', 'amd64'), ('ia32', 'x86'), ('Itanium', 'ia64')]
103"""List of icl platforms"""
104
105@conf
106def gather_ifort_versions(conf, versions):
107	"""
108	List compiler versions by looking up registry keys
109	"""
110	version_pattern = re.compile('^...?.?\....?.?')
111	try:
112		all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Compilers\\Fortran')
113	except OSError:
114		try:
115			all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Intel\\Compilers\\Fortran')
116		except OSError:
117			return
118	index = 0
119	while 1:
120		try:
121			version = Utils.winreg.EnumKey(all_versions, index)
122		except OSError:
123			break
124		index += 1
125		if not version_pattern.match(version):
126			continue
127		targets = {}
128		for target,arch in all_ifort_platforms:
129			if target=='intel64':
130				targetDir='EM64T_NATIVE'
131			else:
132				targetDir=target
133			try:
134				Utils.winreg.OpenKey(all_versions,version+'\\'+targetDir)
135				icl_version=Utils.winreg.OpenKey(all_versions,version)
136				path,type=Utils.winreg.QueryValueEx(icl_version,'ProductDir')
137			except OSError:
138				pass
139			else:
140				batch_file=os.path.join(path,'bin','ifortvars.bat')
141				if os.path.isfile(batch_file):
142					targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
143
144		for target,arch in all_ifort_platforms:
145			try:
146				icl_version = Utils.winreg.OpenKey(all_versions, version+'\\'+target)
147				path,type = Utils.winreg.QueryValueEx(icl_version,'ProductDir')
148			except OSError:
149				continue
150			else:
151				batch_file=os.path.join(path,'bin','ifortvars.bat')
152				if os.path.isfile(batch_file):
153					targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
154		major = version[0:2]
155		versions['intel ' + major] = targets
156
157@conf
158def setup_ifort(conf, versiondict):
159	"""
160	Checks installed compilers and targets and returns the first combination from the user's
161	options, env, or the global supported lists that checks.
162
163	:param versiondict: dict(platform -> dict(architecture -> configuration))
164	:type versiondict: dict(string -> dict(string -> target_compiler)
165	:return: the compiler, revision, path, include dirs, library paths and target architecture
166	:rtype: tuple of strings
167	"""
168	platforms = Utils.to_list(conf.env.MSVC_TARGETS) or [i for i,j in all_ifort_platforms]
169	desired_versions = conf.env.MSVC_VERSIONS or list(reversed(list(versiondict.keys())))
170	for version in desired_versions:
171		try:
172			targets = versiondict[version]
173		except KeyError:
174			continue
175		for arch in platforms:
176			try:
177				cfg = targets[arch]
178			except KeyError:
179				continue
180			cfg.evaluate()
181			if cfg.is_valid:
182				compiler,revision = version.rsplit(' ', 1)
183				return compiler,revision,cfg.bindirs,cfg.incdirs,cfg.libdirs,cfg.cpu
184	conf.fatal('ifort: Impossible to find a valid architecture for building %r - %r' % (desired_versions, list(versiondict.keys())))
185
186@conf
187def get_ifort_version_win32(conf, compiler, version, target, vcvars):
188	# FIXME hack
189	try:
190		conf.msvc_cnt += 1
191	except AttributeError:
192		conf.msvc_cnt = 1
193	batfile = conf.bldnode.make_node('waf-print-msvc-%d.bat' % conf.msvc_cnt)
194	batfile.write("""@echo off
195set INCLUDE=
196set LIB=
197call "%s" %s
198echo PATH=%%PATH%%
199echo INCLUDE=%%INCLUDE%%
200echo LIB=%%LIB%%;%%LIBPATH%%
201""" % (vcvars,target))
202	sout = conf.cmd_and_log(['cmd.exe', '/E:on', '/V:on', '/C', batfile.abspath()])
203	batfile.delete()
204	lines = sout.splitlines()
205
206	if not lines[0]:
207		lines.pop(0)
208
209	MSVC_PATH = MSVC_INCDIR = MSVC_LIBDIR = None
210	for line in lines:
211		if line.startswith('PATH='):
212			path = line[5:]
213			MSVC_PATH = path.split(';')
214		elif line.startswith('INCLUDE='):
215			MSVC_INCDIR = [i for i in line[8:].split(';') if i]
216		elif line.startswith('LIB='):
217			MSVC_LIBDIR = [i for i in line[4:].split(';') if i]
218	if None in (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR):
219		conf.fatal('ifort: Could not find a valid architecture for building (get_ifort_version_win32)')
220
221	# Check if the compiler is usable at all.
222	# The detection may return 64-bit versions even on 32-bit systems, and these would fail to run.
223	env = dict(os.environ)
224	env.update(PATH = path)
225	compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
226	fc = conf.find_program(compiler_name, path_list=MSVC_PATH)
227
228	# delete CL if exists. because it could contain parameters which can change cl's behaviour rather catastrophically.
229	if 'CL' in env:
230		del(env['CL'])
231
232	try:
233		conf.cmd_and_log(fc + ['/help'], env=env)
234	except UnicodeError:
235		st = traceback.format_exc()
236		if conf.logger:
237			conf.logger.error(st)
238		conf.fatal('ifort: Unicode error - check the code page?')
239	except Exception as e:
240		Logs.debug('ifort: get_ifort_version: %r %r %r -> failure %s', compiler, version, target, str(e))
241		conf.fatal('ifort: cannot run the compiler in get_ifort_version (run with -v to display errors)')
242	else:
243		Logs.debug('ifort: get_ifort_version: %r %r %r -> OK', compiler, version, target)
244	finally:
245		conf.env[compiler_name] = ''
246
247	return (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR)
248
249class target_compiler(object):
250	"""
251	Wraps a compiler configuration; call evaluate() to determine
252	whether the configuration is usable.
253	"""
254	def __init__(self, ctx, compiler, cpu, version, bat_target, bat, callback=None):
255		"""
256		:param ctx: configuration context to use to eventually get the version environment
257		:param compiler: compiler name
258		:param cpu: target cpu
259		:param version: compiler version number
260		:param bat_target: ?
261		:param bat: path to the batch file to run
262		:param callback: optional function to take the realized environment variables tup and map it (e.g. to combine other constant paths)
263		"""
264		self.conf = ctx
265		self.name = None
266		self.is_valid = False
267		self.is_done = False
268
269		self.compiler = compiler
270		self.cpu = cpu
271		self.version = version
272		self.bat_target = bat_target
273		self.bat = bat
274		self.callback = callback
275
276	def evaluate(self):
277		if self.is_done:
278			return
279		self.is_done = True
280		try:
281			vs = self.conf.get_ifort_version_win32(self.compiler, self.version, self.bat_target, self.bat)
282		except Errors.ConfigurationError:
283			self.is_valid = False
284			return
285		if self.callback:
286			vs = self.callback(self, vs)
287		self.is_valid = True
288		(self.bindirs, self.incdirs, self.libdirs) = vs
289
290	def __str__(self):
291		return str((self.bindirs, self.incdirs, self.libdirs))
292
293	def __repr__(self):
294		return repr((self.bindirs, self.incdirs, self.libdirs))
295
296@conf
297def detect_ifort(self):
298	return self.setup_ifort(self.get_ifort_versions(False))
299
300@conf
301def get_ifort_versions(self, eval_and_save=True):
302	"""
303	:return: platforms to compiler configurations
304	:rtype: dict
305	"""
306	dct = {}
307	self.gather_ifort_versions(dct)
308	return dct
309
310def _get_prog_names(self, compiler):
311	if compiler=='intel':
312		compiler_name = 'ifort'
313		linker_name = 'XILINK'
314		lib_name = 'XILIB'
315	else:
316		# assumes CL.exe
317		compiler_name = 'CL'
318		linker_name = 'LINK'
319		lib_name = 'LIB'
320	return compiler_name, linker_name, lib_name
321
322@conf
323def find_ifort_win32(conf):
324	# the autodetection is supposed to be performed before entering in this method
325	v = conf.env
326	path = v.PATH
327	compiler = v.MSVC_COMPILER
328	version = v.MSVC_VERSION
329
330	compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
331	v.IFORT_MANIFEST = (compiler == 'intel' and version >= 11)
332
333	# compiler
334	fc = conf.find_program(compiler_name, var='FC', path_list=path)
335
336	# before setting anything, check if the compiler is really intel fortran
337	env = dict(conf.environ)
338	if path:
339		env.update(PATH = ';'.join(path))
340	if not conf.cmd_and_log(fc + ['/nologo', '/help'], env=env):
341		conf.fatal('not intel fortran compiler could not be identified')
342
343	v.FC_NAME = 'IFORT'
344
345	if not v.LINK_FC:
346		conf.find_program(linker_name, var='LINK_FC', path_list=path, mandatory=True)
347
348	if not v.AR:
349		conf.find_program(lib_name, path_list=path, var='AR', mandatory=True)
350		v.ARFLAGS = ['/nologo']
351
352	# manifest tool. Not required for VS 2003 and below. Must have for VS 2005 and later
353	if v.IFORT_MANIFEST:
354		conf.find_program('MT', path_list=path, var='MT')
355		v.MTFLAGS = ['/nologo']
356
357	try:
358		conf.load('winres')
359	except Errors.WafError:
360		Logs.warn('Resource compiler not found. Compiling resource file is disabled')
361
362#######################################################################################################
363##### conf above, build below
364
365@after_method('apply_link')
366@feature('fc')
367def apply_flags_ifort(self):
368	"""
369	Adds additional flags implied by msvc, such as subsystems and pdb files::
370
371		def build(bld):
372			bld.stlib(source='main.c', target='bar', subsystem='gruik')
373	"""
374	if not self.env.IFORT_WIN32 or not getattr(self, 'link_task', None):
375		return
376
377	is_static = isinstance(self.link_task, ccroot.stlink_task)
378
379	subsystem = getattr(self, 'subsystem', '')
380	if subsystem:
381		subsystem = '/subsystem:%s' % subsystem
382		flags = is_static and 'ARFLAGS' or 'LINKFLAGS'
383		self.env.append_value(flags, subsystem)
384
385	if not is_static:
386		for f in self.env.LINKFLAGS:
387			d = f.lower()
388			if d[1:] == 'debug':
389				pdbnode = self.link_task.outputs[0].change_ext('.pdb')
390				self.link_task.outputs.append(pdbnode)
391
392				if getattr(self, 'install_task', None):
393					self.pdb_install_task = self.add_install_files(install_to=self.install_task.install_to, install_from=pdbnode)
394
395				break
396
397@feature('fcprogram', 'fcshlib', 'fcprogram_test')
398@after_method('apply_link')
399def apply_manifest_ifort(self):
400	"""
401	Enables manifest embedding in Fortran DLLs when using ifort on Windows
402	See: http://msdn2.microsoft.com/en-us/library/ms235542(VS.80).aspx
403	"""
404	if self.env.IFORT_WIN32 and getattr(self, 'link_task', None):
405		# it seems ifort.exe cannot be called for linking
406		self.link_task.env.FC = self.env.LINK_FC
407
408	if self.env.IFORT_WIN32 and self.env.IFORT_MANIFEST and getattr(self, 'link_task', None):
409		out_node = self.link_task.outputs[0]
410		man_node = out_node.parent.find_or_declare(out_node.name + '.manifest')
411		self.link_task.outputs.append(man_node)
412		self.env.DO_MANIFEST = True
413
414