1#! /usr/bin/env python 2# encoding: utf-8 3# DC 2008 4# Thomas Nagy 2016-2018 (ita) 5 6""" 7Fortran configuration helpers 8""" 9 10import re, os, sys, shlex 11from waflib.Configure import conf 12from waflib.TaskGen import feature, before_method 13 14FC_FRAGMENT = ' program main\n end program main\n' 15FC_FRAGMENT2 = ' PROGRAM MAIN\n END\n' # what's the actual difference between these? 16 17@conf 18def 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 51def 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 61def 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 72def 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 94def 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 112def 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 131def 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 144def 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 180GCC_DRIVER_LINE = re.compile('^Driving:') 181POSIX_STATIC_EXT = re.compile(r'\S+\.a') 182POSIX_LIB_FLAGS = re.compile(r'-l\S+') 183 184@conf 185def 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 195def 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 225LINKFLAGS_IGNORED = [r'-lang*', r'-lcrt[a-zA-Z0-9\.]*\.o', r'-lc$', r'-lSystem', r'-libmil', r'-LIST:*', r'-LNO:*'] 226if os.name == 'nt': 227 LINKFLAGS_IGNORED.extend([r'-lfrt*', r'-luser32', r'-lkernel32', r'-ladvapi32', r'-lmsvcrt', r'-lshell32', r'-lmingw', r'-lmoldname']) 228else: 229 LINKFLAGS_IGNORED.append(r'-lgcc*') 230RLINKFLAGS_IGNORED = [re.compile(f) for f in LINKFLAGS_IGNORED] 231 232def _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 239def 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 248SPACE_OPTS = re.compile('^-[LRuYz]$') 249NOSPACE_OPTS = re.compile('^-[RL]') 250 251def _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 290def _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 304def 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 332def 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 362ROUTINES_CODE = """\ 363 subroutine foobar() 364 return 365 end 366 subroutine foo_bar() 367 return 368 end 369""" 370 371MAIN_CODE = """ 372void %(dummy_func_nounder)s(void); 373void %(dummy_func_under)s(void); 374int %(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') 383def 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 396def 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 407def 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 412def 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') 450def 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 455def 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 476def 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() 489