waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

fc_config.py (13986B)


      1 #! /usr/bin/env python
      2 # encoding: utf-8
      3 # DC 2008
      4 # Thomas Nagy 2016-2018 (ita)
      5 
      6 """
      7 Fortran configuration helpers
      8 """
      9 
     10 import re, os, sys, shlex
     11 from waflib.Configure import conf
     12 from waflib.TaskGen import feature, before_method
     13 
     14 FC_FRAGMENT = '        program main\n        end     program main\n'
     15 FC_FRAGMENT2 = '        PROGRAM MAIN\n        END\n' # what's the actual difference between these?
     16 
     17 @conf
     18 def fc_flags(conf):
     19 	"""
     20 	Defines common fortran configuration flags and file extensions
     21 	"""
     22 	v = conf.env
     23 
     24 	v.FC_SRC_F    = []
     25 	v.FC_TGT_F    = ['-c', '-o']
     26 	v.FCINCPATH_ST  = '-I%s'
     27 	v.FCDEFINES_ST  = '-D%s'
     28 
     29 	if not v.LINK_FC:
     30 		v.LINK_FC = v.FC
     31 
     32 	v.FCLNK_SRC_F = []
     33 	v.FCLNK_TGT_F = ['-o']
     34 
     35 	v.FCFLAGS_fcshlib   = ['-fpic']
     36 	v.LINKFLAGS_fcshlib = ['-shared']
     37 	v.fcshlib_PATTERN   = 'lib%s.so'
     38 
     39 	v.fcstlib_PATTERN   = 'lib%s.a'
     40 
     41 	v.FCLIB_ST       = '-l%s'
     42 	v.FCLIBPATH_ST   = '-L%s'
     43 	v.FCSTLIB_ST     = '-l%s'
     44 	v.FCSTLIBPATH_ST = '-L%s'
     45 	v.FCSTLIB_MARKER = '-Wl,-Bstatic'
     46 	v.FCSHLIB_MARKER = '-Wl,-Bdynamic'
     47 
     48 	v.SONAME_ST      = '-Wl,-h,%s'
     49 
     50 @conf
     51 def fc_add_flags(conf):
     52 	"""
     53 	Adds FCFLAGS / LDFLAGS / LINKFLAGS from os.environ to conf.env
     54 	"""
     55 	conf.add_os_flags('FCPPFLAGS', dup=False)
     56 	conf.add_os_flags('FCFLAGS', dup=False)
     57 	conf.add_os_flags('LINKFLAGS', dup=False)
     58 	conf.add_os_flags('LDFLAGS', dup=False)
     59 
     60 @conf
     61 def check_fortran(self, *k, **kw):
     62 	"""
     63 	Compiles a Fortran program to ensure that the settings are correct
     64 	"""
     65 	self.check_cc(
     66 		fragment         = FC_FRAGMENT,
     67 		compile_filename = 'test.f',
     68 		features         = 'fc fcprogram',
     69 		msg              = 'Compiling a simple fortran app')
     70 
     71 @conf
     72 def check_fc(self, *k, **kw):
     73 	"""
     74 	Same as :py:func:`waflib.Tools.c_config.check` but defaults to the *Fortran* programming language
     75 	(this overrides the C defaults in :py:func:`waflib.Tools.c_config.validate_c`)
     76 	"""
     77 	kw['compiler'] = 'fc'
     78 	if not 'compile_mode' in kw:
     79 		kw['compile_mode'] = 'fc'
     80 	if not 'type' in kw:
     81 		kw['type'] = 'fcprogram'
     82 	if not 'compile_filename' in kw:
     83 		kw['compile_filename'] = 'test.f90'
     84 	if not 'code' in kw:
     85 		kw['code'] = FC_FRAGMENT
     86 	return self.check(*k, **kw)
     87 
     88 # ------------------------------------------------------------------------
     89 # --- These are the default platform modifiers, refactored here for
     90 #     convenience.  gfortran and g95 have much overlap.
     91 # ------------------------------------------------------------------------
     92 
     93 @conf
     94 def fortran_modifier_darwin(conf):
     95 	"""
     96 	Defines Fortran flags and extensions for OSX systems
     97 	"""
     98 	v = conf.env
     99 	v.FCFLAGS_fcshlib   = ['-fPIC']
    100 	v.LINKFLAGS_fcshlib = ['-dynamiclib']
    101 	v.fcshlib_PATTERN   = 'lib%s.dylib'
    102 	v.FRAMEWORKPATH_ST  = '-F%s'
    103 	v.FRAMEWORK_ST      = ['-framework']
    104 
    105 	v.LINKFLAGS_fcstlib = []
    106 
    107 	v.FCSHLIB_MARKER    = ''
    108 	v.FCSTLIB_MARKER    = ''
    109 	v.SONAME_ST         = ''
    110 
    111 @conf
    112 def fortran_modifier_win32(conf):
    113 	"""
    114 	Defines Fortran flags for Windows platforms
    115 	"""
    116 	v = conf.env
    117 	v.fcprogram_PATTERN = v.fcprogram_test_PATTERN  = '%s.exe'
    118 
    119 	v.fcshlib_PATTERN   = '%s.dll'
    120 	v.implib_PATTERN    = '%s.dll.a'
    121 	v.IMPLIB_ST         = '-Wl,--out-implib,%s'
    122 
    123 	v.FCFLAGS_fcshlib   = []
    124 
    125 	# Auto-import is enabled by default even without this option,
    126 	# but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
    127 	# that the linker emits otherwise.
    128 	v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
    129 
    130 @conf
    131 def fortran_modifier_cygwin(conf):
    132 	"""
    133 	Defines Fortran flags for use on cygwin
    134 	"""
    135 	fortran_modifier_win32(conf)
    136 	v = conf.env
    137 	v.fcshlib_PATTERN = 'cyg%s.dll'
    138 	v.append_value('LINKFLAGS_fcshlib', ['-Wl,--enable-auto-image-base'])
    139 	v.FCFLAGS_fcshlib = []
    140 
    141 # ------------------------------------------------------------------------
    142 
    143 @conf
    144 def check_fortran_dummy_main(self, *k, **kw):
    145 	"""
    146 	Determines if a main function is needed by compiling a code snippet with
    147 	the C compiler and linking it with the Fortran compiler (useful on unix-like systems)
    148 	"""
    149 	if not self.env.CC:
    150 		self.fatal('A c compiler is required for check_fortran_dummy_main')
    151 
    152 	lst = ['MAIN__', '__MAIN', '_MAIN', 'MAIN_', 'MAIN']
    153 	lst.extend([m.lower() for m in lst])
    154 	lst.append('')
    155 
    156 	self.start_msg('Detecting whether we need a dummy main')
    157 	for main in lst:
    158 		kw['fortran_main'] = main
    159 		try:
    160 			self.check_cc(
    161 				fragment = 'int %s() { return 0; }\n' % (main or 'test'),
    162 				features = 'c fcprogram',
    163 				mandatory = True
    164 			)
    165 			if not main:
    166 				self.env.FC_MAIN = -1
    167 				self.end_msg('no')
    168 			else:
    169 				self.env.FC_MAIN = main
    170 				self.end_msg('yes %s' % main)
    171 			break
    172 		except self.errors.ConfigurationError:
    173 			pass
    174 	else:
    175 		self.end_msg('not found')
    176 		self.fatal('could not detect whether fortran requires a dummy main, see the config.log')
    177 
    178 # ------------------------------------------------------------------------
    179 
    180 GCC_DRIVER_LINE = re.compile('^Driving:')
    181 POSIX_STATIC_EXT = re.compile(r'\S+\.a')
    182 POSIX_LIB_FLAGS = re.compile(r'-l\S+')
    183 
    184 @conf
    185 def is_link_verbose(self, txt):
    186 	"""Returns True if 'useful' link options can be found in txt"""
    187 	assert isinstance(txt, str)
    188 	for line in txt.splitlines():
    189 		if not GCC_DRIVER_LINE.search(line):
    190 			if POSIX_STATIC_EXT.search(line) or POSIX_LIB_FLAGS.search(line):
    191 				return True
    192 	return False
    193 
    194 @conf
    195 def check_fortran_verbose_flag(self, *k, **kw):
    196 	"""
    197 	Checks what kind of verbose (-v) flag works, then sets it to env.FC_VERBOSE_FLAG
    198 	"""
    199 	self.start_msg('fortran link verbose flag')
    200 	for x in ('-v', '--verbose', '-verbose', '-V'):
    201 		try:
    202 			self.check_cc(
    203 				features = 'fc fcprogram_test',
    204 				fragment = FC_FRAGMENT2,
    205 				compile_filename = 'test.f',
    206 				linkflags = [x],
    207 				mandatory=True)
    208 		except self.errors.ConfigurationError:
    209 			pass
    210 		else:
    211 			# output is on stderr or stdout (for xlf)
    212 			if self.is_link_verbose(self.test_bld.err) or self.is_link_verbose(self.test_bld.out):
    213 				self.end_msg(x)
    214 				break
    215 	else:
    216 		self.end_msg('failure')
    217 		self.fatal('Could not obtain the fortran link verbose flag (see config.log)')
    218 
    219 	self.env.FC_VERBOSE_FLAG = x
    220 	return x
    221 
    222 # ------------------------------------------------------------------------
    223 
    224 # linkflags which match those are ignored
    225 LINKFLAGS_IGNORED = [r'-lang*', r'-lcrt[a-zA-Z0-9\.]*\.o', r'-lc$', r'-lSystem', r'-libmil', r'-LIST:*', r'-LNO:*']
    226 if os.name == 'nt':
    227 	LINKFLAGS_IGNORED.extend([r'-lfrt*', r'-luser32', r'-lkernel32', r'-ladvapi32', r'-lmsvcrt', r'-lshell32', r'-lmingw', r'-lmoldname'])
    228 else:
    229 	LINKFLAGS_IGNORED.append(r'-lgcc*')
    230 RLINKFLAGS_IGNORED = [re.compile(f) for f in LINKFLAGS_IGNORED]
    231 
    232 def _match_ignore(line):
    233 	"""Returns True if the line should be ignored (Fortran verbose flag test)"""
    234 	for i in RLINKFLAGS_IGNORED:
    235 		if i.match(line):
    236 			return True
    237 	return False
    238 
    239 def parse_fortran_link(lines):
    240 	"""Given the output of verbose link of Fortran compiler, this returns a
    241 	list of flags necessary for linking using the standard linker."""
    242 	final_flags = []
    243 	for line in lines:
    244 		if not GCC_DRIVER_LINE.match(line):
    245 			_parse_flink_line(line, final_flags)
    246 	return final_flags
    247 
    248 SPACE_OPTS = re.compile('^-[LRuYz]$')
    249 NOSPACE_OPTS = re.compile('^-[RL]')
    250 
    251 def _parse_flink_token(lexer, token, tmp_flags):
    252 	# Here we go (convention for wildcard is shell, not regex !)
    253 	#   1 TODO: we first get some root .a libraries
    254 	#   2 TODO: take everything starting by -bI:*
    255 	#   3 Ignore the following flags: -lang* | -lcrt*.o | -lc |
    256 	#   -lgcc* | -lSystem | -libmil | -LANG:=* | -LIST:* | -LNO:*)
    257 	#   4 take into account -lkernel32
    258 	#   5 For options of the kind -[[LRuYz]], as they take one argument
    259 	#   after, the actual option is the next token
    260 	#   6 For -YP,*: take and replace by -Larg where arg is the old
    261 	#   argument
    262 	#   7 For -[lLR]*: take
    263 
    264 	# step 3
    265 	if _match_ignore(token):
    266 		pass
    267 	# step 4
    268 	elif token.startswith('-lkernel32') and sys.platform == 'cygwin':
    269 		tmp_flags.append(token)
    270 	# step 5
    271 	elif SPACE_OPTS.match(token):
    272 		t = lexer.get_token()
    273 		if t.startswith('P,'):
    274 			t = t[2:]
    275 		for opt in t.split(os.pathsep):
    276 			tmp_flags.append('-L%s' % opt)
    277 	# step 6
    278 	elif NOSPACE_OPTS.match(token):
    279 		tmp_flags.append(token)
    280 	# step 7
    281 	elif POSIX_LIB_FLAGS.match(token):
    282 		tmp_flags.append(token)
    283 	else:
    284 		# ignore anything not explicitly taken into account
    285 		pass
    286 
    287 	t = lexer.get_token()
    288 	return t
    289 
    290 def _parse_flink_line(line, final_flags):
    291 	"""private"""
    292 	lexer = shlex.shlex(line, posix = True)
    293 	lexer.whitespace_split = True
    294 
    295 	t = lexer.get_token()
    296 	tmp_flags = []
    297 	while t:
    298 		t = _parse_flink_token(lexer, t, tmp_flags)
    299 
    300 	final_flags.extend(tmp_flags)
    301 	return final_flags
    302 
    303 @conf
    304 def check_fortran_clib(self, autoadd=True, *k, **kw):
    305 	"""
    306 	Obtains the flags for linking with the C library
    307 	if this check works, add uselib='CLIB' to your task generators
    308 	"""
    309 	if not self.env.FC_VERBOSE_FLAG:
    310 		self.fatal('env.FC_VERBOSE_FLAG is not set: execute check_fortran_verbose_flag?')
    311 
    312 	self.start_msg('Getting fortran runtime link flags')
    313 	try:
    314 		self.check_cc(
    315 			fragment = FC_FRAGMENT2,
    316 			compile_filename = 'test.f',
    317 			features = 'fc fcprogram_test',
    318 			linkflags = [self.env.FC_VERBOSE_FLAG]
    319 		)
    320 	except Exception:
    321 		self.end_msg(False)
    322 		if kw.get('mandatory', True):
    323 			conf.fatal('Could not find the c library flags')
    324 	else:
    325 		out = self.test_bld.err
    326 		flags = parse_fortran_link(out.splitlines())
    327 		self.end_msg('ok (%s)' % ' '.join(flags))
    328 		self.env.LINKFLAGS_CLIB = flags
    329 		return flags
    330 	return []
    331 
    332 def getoutput(conf, cmd, stdin=False):
    333 	"""
    334 	Obtains Fortran command outputs
    335 	"""
    336 	from waflib import Errors
    337 	if conf.env.env:
    338 		env = conf.env.env
    339 	else:
    340 		env = dict(os.environ)
    341 		env['LANG'] = 'C'
    342 	input = stdin and '\n'.encode() or None
    343 	try:
    344 		out, err = conf.cmd_and_log(cmd, env=env, output=0, input=input)
    345 	except Errors.WafError as e:
    346 		# An WafError might indicate an error code during the command
    347 		# execution, in this case we still obtain the stderr and stdout,
    348 		# which we can use to find the version string.
    349 		if not (hasattr(e, 'stderr') and hasattr(e, 'stdout')):
    350 			raise e
    351 		else:
    352 			# Ignore the return code and return the original
    353 			# stdout and stderr.
    354 			out = e.stdout
    355 			err = e.stderr
    356 	except Exception:
    357 		conf.fatal('could not determine the compiler version %r' % cmd)
    358 	return (out, err)
    359 
    360 # ------------------------------------------------------------------------
    361 
    362 ROUTINES_CODE = """\
    363       subroutine foobar()
    364       return
    365       end
    366       subroutine foo_bar()
    367       return
    368       end
    369 """
    370 
    371 MAIN_CODE = """
    372 void %(dummy_func_nounder)s(void);
    373 void %(dummy_func_under)s(void);
    374 int %(main_func_name)s() {
    375   %(dummy_func_nounder)s();
    376   %(dummy_func_under)s();
    377   return 0;
    378 }
    379 """
    380 
    381 @feature('link_main_routines_func')
    382 @before_method('process_source')
    383 def link_main_routines_tg_method(self):
    384 	"""
    385 	The configuration test declares a unique task generator,
    386 	so we create other task generators from there for fortran link tests
    387 	"""
    388 	def write_test_file(task):
    389 		task.outputs[0].write(task.generator.code)
    390 	bld = self.bld
    391 	bld(rule=write_test_file, target='main.c', code=MAIN_CODE % self.__dict__)
    392 	bld(rule=write_test_file, target='test.f', code=ROUTINES_CODE)
    393 	bld(features='fc fcstlib', source='test.f', target='test')
    394 	bld(features='c fcprogram', source='main.c', target='app', use='test')
    395 
    396 def mangling_schemes():
    397 	"""
    398 	Generate triplets for use with mangle_name
    399 	(used in check_fortran_mangling)
    400 	the order is tuned for gfortan
    401 	"""
    402 	for u in ('_', ''):
    403 		for du in ('', '_'):
    404 			for c in ("lower", "upper"):
    405 				yield (u, du, c)
    406 
    407 def mangle_name(u, du, c, name):
    408 	"""Mangle a name from a triplet (used in check_fortran_mangling)"""
    409 	return getattr(name, c)() + u + (name.find('_') != -1 and du or '')
    410 
    411 @conf
    412 def check_fortran_mangling(self, *k, **kw):
    413 	"""
    414 	Detect the mangling scheme, sets FORTRAN_MANGLING to the triplet found
    415 
    416 	This test will compile a fortran static library, then link a c app against it
    417 	"""
    418 	if not self.env.CC:
    419 		self.fatal('A c compiler is required for link_main_routines')
    420 	if not self.env.FC:
    421 		self.fatal('A fortran compiler is required for link_main_routines')
    422 	if not self.env.FC_MAIN:
    423 		self.fatal('Checking for mangling requires self.env.FC_MAIN (execute "check_fortran_dummy_main" first?)')
    424 
    425 	self.start_msg('Getting fortran mangling scheme')
    426 	for (u, du, c) in mangling_schemes():
    427 		try:
    428 			self.check_cc(
    429 				compile_filename   = [],
    430 				features           = 'link_main_routines_func',
    431 				msg                = 'nomsg',
    432 				errmsg             = 'nomsg',
    433 				dummy_func_nounder = mangle_name(u, du, c, 'foobar'),
    434 				dummy_func_under   = mangle_name(u, du, c, 'foo_bar'),
    435 				main_func_name     = self.env.FC_MAIN
    436 			)
    437 		except self.errors.ConfigurationError:
    438 			pass
    439 		else:
    440 			self.end_msg("ok ('%s', '%s', '%s-case')" % (u, du, c))
    441 			self.env.FORTRAN_MANGLING = (u, du, c)
    442 			break
    443 	else:
    444 		self.end_msg(False)
    445 		self.fatal('mangler not found')
    446 	return (u, du, c)
    447 
    448 @feature('pyext')
    449 @before_method('propagate_uselib_vars', 'apply_link')
    450 def set_lib_pat(self):
    451 	"""Sets the Fortran flags for linking with Python"""
    452 	self.env.fcshlib_PATTERN = self.env.pyext_PATTERN
    453 
    454 @conf
    455 def detect_openmp(self):
    456 	"""
    457 	Detects openmp flags and sets the OPENMP ``FCFLAGS``/``LINKFLAGS``
    458 	"""
    459 	for x in ('-fopenmp','-openmp','-mp','-xopenmp','-omp','-qsmp=omp'):
    460 		try:
    461 			self.check_fc(
    462 				msg          = 'Checking for OpenMP flag %s' % x,
    463 				fragment     = 'program main\n  call omp_get_num_threads()\nend program main',
    464 				fcflags      = x,
    465 				linkflags    = x,
    466 				uselib_store = 'OPENMP'
    467 			)
    468 		except self.errors.ConfigurationError:
    469 			pass
    470 		else:
    471 			break
    472 	else:
    473 		self.fatal('Could not find OpenMP')
    474 
    475 @conf
    476 def check_gfortran_o_space(self):
    477 	if self.env.FC_NAME != 'GFORTRAN' or int(self.env.FC_VERSION[0]) > 4:
    478 		# This is for old compilers and only for gfortran.
    479 		# No idea how other implementations handle this. Be safe and bail out.
    480 		return
    481 	self.env.stash()
    482 	self.env.FCLNK_TGT_F = ['-o', '']
    483 	try:
    484 		self.check_fc(msg='Checking if the -o link must be split from arguments', fragment=FC_FRAGMENT, features='fc fcshlib')
    485 	except self.errors.ConfigurationError:
    486 		self.env.revert()
    487 	else:
    488 		self.env.commit()