123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- #! /usr/bin/env python
- # encoding: utf-8
- # DC 2008
- # Thomas Nagy 2016-2018 (ita)
- """
- Fortran configuration helpers
- """
- import re, os, sys, shlex
- from waflib.Configure import conf
- from waflib.TaskGen import feature, before_method
- FC_FRAGMENT = ' program main\n end program main\n'
- FC_FRAGMENT2 = ' PROGRAM MAIN\n END\n' # what's the actual difference between these?
- @conf
- def fc_flags(conf):
- """
- Defines common fortran configuration flags and file extensions
- """
- v = conf.env
- v.FC_SRC_F = []
- v.FC_TGT_F = ['-c', '-o']
- v.FCINCPATH_ST = '-I%s'
- v.FCDEFINES_ST = '-D%s'
- if not v.LINK_FC:
- v.LINK_FC = v.FC
- v.FCLNK_SRC_F = []
- v.FCLNK_TGT_F = ['-o']
- v.FCFLAGS_fcshlib = ['-fpic']
- v.LINKFLAGS_fcshlib = ['-shared']
- v.fcshlib_PATTERN = 'lib%s.so'
- v.fcstlib_PATTERN = 'lib%s.a'
- v.FCLIB_ST = '-l%s'
- v.FCLIBPATH_ST = '-L%s'
- v.FCSTLIB_ST = '-l%s'
- v.FCSTLIBPATH_ST = '-L%s'
- v.FCSTLIB_MARKER = '-Wl,-Bstatic'
- v.FCSHLIB_MARKER = '-Wl,-Bdynamic'
- v.SONAME_ST = '-Wl,-h,%s'
- @conf
- def fc_add_flags(conf):
- """
- Adds FCFLAGS / LDFLAGS / LINKFLAGS from os.environ to conf.env
- """
- conf.add_os_flags('FCPPFLAGS', dup=False)
- conf.add_os_flags('FCFLAGS', dup=False)
- conf.add_os_flags('LINKFLAGS', dup=False)
- conf.add_os_flags('LDFLAGS', dup=False)
- @conf
- def check_fortran(self, *k, **kw):
- """
- Compiles a Fortran program to ensure that the settings are correct
- """
- self.check_cc(
- fragment = FC_FRAGMENT,
- compile_filename = 'test.f',
- features = 'fc fcprogram',
- msg = 'Compiling a simple fortran app')
- @conf
- def check_fc(self, *k, **kw):
- """
- Same as :py:func:`waflib.Tools.c_config.check` but defaults to the *Fortran* programming language
- (this overrides the C defaults in :py:func:`waflib.Tools.c_config.validate_c`)
- """
- kw['compiler'] = 'fc'
- if not 'compile_mode' in kw:
- kw['compile_mode'] = 'fc'
- if not 'type' in kw:
- kw['type'] = 'fcprogram'
- if not 'compile_filename' in kw:
- kw['compile_filename'] = 'test.f90'
- if not 'code' in kw:
- kw['code'] = FC_FRAGMENT
- return self.check(*k, **kw)
- # ------------------------------------------------------------------------
- # --- These are the default platform modifiers, refactored here for
- # convenience. gfortran and g95 have much overlap.
- # ------------------------------------------------------------------------
- @conf
- def fortran_modifier_darwin(conf):
- """
- Defines Fortran flags and extensions for OSX systems
- """
- v = conf.env
- v.FCFLAGS_fcshlib = ['-fPIC']
- v.LINKFLAGS_fcshlib = ['-dynamiclib']
- v.fcshlib_PATTERN = 'lib%s.dylib'
- v.FRAMEWORKPATH_ST = '-F%s'
- v.FRAMEWORK_ST = ['-framework']
- v.LINKFLAGS_fcstlib = []
- v.FCSHLIB_MARKER = ''
- v.FCSTLIB_MARKER = ''
- v.SONAME_ST = ''
- @conf
- def fortran_modifier_win32(conf):
- """
- Defines Fortran flags for Windows platforms
- """
- v = conf.env
- v.fcprogram_PATTERN = v.fcprogram_test_PATTERN = '%s.exe'
- v.fcshlib_PATTERN = '%s.dll'
- v.implib_PATTERN = '%s.dll.a'
- v.IMPLIB_ST = '-Wl,--out-implib,%s'
- v.FCFLAGS_fcshlib = []
- # Auto-import is enabled by default even without this option,
- # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
- # that the linker emits otherwise.
- v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
- @conf
- def fortran_modifier_cygwin(conf):
- """
- Defines Fortran flags for use on cygwin
- """
- fortran_modifier_win32(conf)
- v = conf.env
- v.fcshlib_PATTERN = 'cyg%s.dll'
- v.append_value('LINKFLAGS_fcshlib', ['-Wl,--enable-auto-image-base'])
- v.FCFLAGS_fcshlib = []
- # ------------------------------------------------------------------------
- @conf
- def check_fortran_dummy_main(self, *k, **kw):
- """
- Determines if a main function is needed by compiling a code snippet with
- the C compiler and linking it with the Fortran compiler (useful on unix-like systems)
- """
- if not self.env.CC:
- self.fatal('A c compiler is required for check_fortran_dummy_main')
- lst = ['MAIN__', '__MAIN', '_MAIN', 'MAIN_', 'MAIN']
- lst.extend([m.lower() for m in lst])
- lst.append('')
- self.start_msg('Detecting whether we need a dummy main')
- for main in lst:
- kw['fortran_main'] = main
- try:
- self.check_cc(
- fragment = 'int %s() { return 0; }\n' % (main or 'test'),
- features = 'c fcprogram',
- mandatory = True
- )
- if not main:
- self.env.FC_MAIN = -1
- self.end_msg('no')
- else:
- self.env.FC_MAIN = main
- self.end_msg('yes %s' % main)
- break
- except self.errors.ConfigurationError:
- pass
- else:
- self.end_msg('not found')
- self.fatal('could not detect whether fortran requires a dummy main, see the config.log')
- # ------------------------------------------------------------------------
- GCC_DRIVER_LINE = re.compile('^Driving:')
- POSIX_STATIC_EXT = re.compile('\S+\.a')
- POSIX_LIB_FLAGS = re.compile('-l\S+')
- @conf
- def is_link_verbose(self, txt):
- """Returns True if 'useful' link options can be found in txt"""
- assert isinstance(txt, str)
- for line in txt.splitlines():
- if not GCC_DRIVER_LINE.search(line):
- if POSIX_STATIC_EXT.search(line) or POSIX_LIB_FLAGS.search(line):
- return True
- return False
- @conf
- def check_fortran_verbose_flag(self, *k, **kw):
- """
- Checks what kind of verbose (-v) flag works, then sets it to env.FC_VERBOSE_FLAG
- """
- self.start_msg('fortran link verbose flag')
- for x in ('-v', '--verbose', '-verbose', '-V'):
- try:
- self.check_cc(
- features = 'fc fcprogram_test',
- fragment = FC_FRAGMENT2,
- compile_filename = 'test.f',
- linkflags = [x],
- mandatory=True)
- except self.errors.ConfigurationError:
- pass
- else:
- # output is on stderr or stdout (for xlf)
- if self.is_link_verbose(self.test_bld.err) or self.is_link_verbose(self.test_bld.out):
- self.end_msg(x)
- break
- else:
- self.end_msg('failure')
- self.fatal('Could not obtain the fortran link verbose flag (see config.log)')
- self.env.FC_VERBOSE_FLAG = x
- return x
- # ------------------------------------------------------------------------
- # linkflags which match those are ignored
- LINKFLAGS_IGNORED = [r'-lang*', r'-lcrt[a-zA-Z0-9\.]*\.o', r'-lc$', r'-lSystem', r'-libmil', r'-LIST:*', r'-LNO:*']
- if os.name == 'nt':
- LINKFLAGS_IGNORED.extend([r'-lfrt*', r'-luser32', r'-lkernel32', r'-ladvapi32', r'-lmsvcrt', r'-lshell32', r'-lmingw', r'-lmoldname'])
- else:
- LINKFLAGS_IGNORED.append(r'-lgcc*')
- RLINKFLAGS_IGNORED = [re.compile(f) for f in LINKFLAGS_IGNORED]
- def _match_ignore(line):
- """Returns True if the line should be ignored (Fortran verbose flag test)"""
- for i in RLINKFLAGS_IGNORED:
- if i.match(line):
- return True
- return False
- def parse_fortran_link(lines):
- """Given the output of verbose link of Fortran compiler, this returns a
- list of flags necessary for linking using the standard linker."""
- final_flags = []
- for line in lines:
- if not GCC_DRIVER_LINE.match(line):
- _parse_flink_line(line, final_flags)
- return final_flags
- SPACE_OPTS = re.compile('^-[LRuYz]$')
- NOSPACE_OPTS = re.compile('^-[RL]')
- def _parse_flink_token(lexer, token, tmp_flags):
- # Here we go (convention for wildcard is shell, not regex !)
- # 1 TODO: we first get some root .a libraries
- # 2 TODO: take everything starting by -bI:*
- # 3 Ignore the following flags: -lang* | -lcrt*.o | -lc |
- # -lgcc* | -lSystem | -libmil | -LANG:=* | -LIST:* | -LNO:*)
- # 4 take into account -lkernel32
- # 5 For options of the kind -[[LRuYz]], as they take one argument
- # after, the actual option is the next token
- # 6 For -YP,*: take and replace by -Larg where arg is the old
- # argument
- # 7 For -[lLR]*: take
- # step 3
- if _match_ignore(token):
- pass
- # step 4
- elif token.startswith('-lkernel32') and sys.platform == 'cygwin':
- tmp_flags.append(token)
- # step 5
- elif SPACE_OPTS.match(token):
- t = lexer.get_token()
- if t.startswith('P,'):
- t = t[2:]
- for opt in t.split(os.pathsep):
- tmp_flags.append('-L%s' % opt)
- # step 6
- elif NOSPACE_OPTS.match(token):
- tmp_flags.append(token)
- # step 7
- elif POSIX_LIB_FLAGS.match(token):
- tmp_flags.append(token)
- else:
- # ignore anything not explicitely taken into account
- pass
- t = lexer.get_token()
- return t
- def _parse_flink_line(line, final_flags):
- """private"""
- lexer = shlex.shlex(line, posix = True)
- lexer.whitespace_split = True
- t = lexer.get_token()
- tmp_flags = []
- while t:
- t = _parse_flink_token(lexer, t, tmp_flags)
- final_flags.extend(tmp_flags)
- return final_flags
- @conf
- def check_fortran_clib(self, autoadd=True, *k, **kw):
- """
- Obtains the flags for linking with the C library
- if this check works, add uselib='CLIB' to your task generators
- """
- if not self.env.FC_VERBOSE_FLAG:
- self.fatal('env.FC_VERBOSE_FLAG is not set: execute check_fortran_verbose_flag?')
- self.start_msg('Getting fortran runtime link flags')
- try:
- self.check_cc(
- fragment = FC_FRAGMENT2,
- compile_filename = 'test.f',
- features = 'fc fcprogram_test',
- linkflags = [self.env.FC_VERBOSE_FLAG]
- )
- except Exception:
- self.end_msg(False)
- if kw.get('mandatory', True):
- conf.fatal('Could not find the c library flags')
- else:
- out = self.test_bld.err
- flags = parse_fortran_link(out.splitlines())
- self.end_msg('ok (%s)' % ' '.join(flags))
- self.env.LINKFLAGS_CLIB = flags
- return flags
- return []
- def getoutput(conf, cmd, stdin=False):
- """
- Obtains Fortran command outputs
- """
- from waflib import Errors
- if conf.env.env:
- env = conf.env.env
- else:
- env = dict(os.environ)
- env['LANG'] = 'C'
- input = stdin and '\n'.encode() or None
- try:
- out, err = conf.cmd_and_log(cmd, env=env, output=0, input=input)
- except Errors.WafError as e:
- # An WafError might indicate an error code during the command
- # execution, in this case we still obtain the stderr and stdout,
- # which we can use to find the version string.
- if not (hasattr(e, 'stderr') and hasattr(e, 'stdout')):
- raise e
- else:
- # Ignore the return code and return the original
- # stdout and stderr.
- out = e.stdout
- err = e.stderr
- except Exception:
- conf.fatal('could not determine the compiler version %r' % cmd)
- return (out, err)
- # ------------------------------------------------------------------------
- ROUTINES_CODE = """\
- subroutine foobar()
- return
- end
- subroutine foo_bar()
- return
- end
- """
- MAIN_CODE = """
- void %(dummy_func_nounder)s(void);
- void %(dummy_func_under)s(void);
- int %(main_func_name)s() {
- %(dummy_func_nounder)s();
- %(dummy_func_under)s();
- return 0;
- }
- """
- @feature('link_main_routines_func')
- @before_method('process_source')
- def link_main_routines_tg_method(self):
- """
- The configuration test declares a unique task generator,
- so we create other task generators from there for fortran link tests
- """
- def write_test_file(task):
- task.outputs[0].write(task.generator.code)
- bld = self.bld
- bld(rule=write_test_file, target='main.c', code=MAIN_CODE % self.__dict__)
- bld(rule=write_test_file, target='test.f', code=ROUTINES_CODE)
- bld(features='fc fcstlib', source='test.f', target='test')
- bld(features='c fcprogram', source='main.c', target='app', use='test')
- def mangling_schemes():
- """
- Generate triplets for use with mangle_name
- (used in check_fortran_mangling)
- the order is tuned for gfortan
- """
- for u in ('_', ''):
- for du in ('', '_'):
- for c in ("lower", "upper"):
- yield (u, du, c)
- def mangle_name(u, du, c, name):
- """Mangle a name from a triplet (used in check_fortran_mangling)"""
- return getattr(name, c)() + u + (name.find('_') != -1 and du or '')
- @conf
- def check_fortran_mangling(self, *k, **kw):
- """
- Detect the mangling scheme, sets FORTRAN_MANGLING to the triplet found
- This test will compile a fortran static library, then link a c app against it
- """
- if not self.env.CC:
- self.fatal('A c compiler is required for link_main_routines')
- if not self.env.FC:
- self.fatal('A fortran compiler is required for link_main_routines')
- if not self.env.FC_MAIN:
- self.fatal('Checking for mangling requires self.env.FC_MAIN (execute "check_fortran_dummy_main" first?)')
- self.start_msg('Getting fortran mangling scheme')
- for (u, du, c) in mangling_schemes():
- try:
- self.check_cc(
- compile_filename = [],
- features = 'link_main_routines_func',
- msg = 'nomsg',
- errmsg = 'nomsg',
- dummy_func_nounder = mangle_name(u, du, c, 'foobar'),
- dummy_func_under = mangle_name(u, du, c, 'foo_bar'),
- main_func_name = self.env.FC_MAIN
- )
- except self.errors.ConfigurationError:
- pass
- else:
- self.end_msg("ok ('%s', '%s', '%s-case')" % (u, du, c))
- self.env.FORTRAN_MANGLING = (u, du, c)
- break
- else:
- self.end_msg(False)
- self.fatal('mangler not found')
- return (u, du, c)
- @feature('pyext')
- @before_method('propagate_uselib_vars', 'apply_link')
- def set_lib_pat(self):
- """Sets the Fortran flags for linking with Python"""
- self.env.fcshlib_PATTERN = self.env.pyext_PATTERN
- @conf
- def detect_openmp(self):
- """
- Detects openmp flags and sets the OPENMP ``FCFLAGS``/``LINKFLAGS``
- """
- for x in ('-fopenmp','-openmp','-mp','-xopenmp','-omp','-qsmp=omp'):
- try:
- self.check_fc(
- msg = 'Checking for OpenMP flag %s' % x,
- fragment = 'program main\n call omp_get_num_threads()\nend program main',
- fcflags = x,
- linkflags = x,
- uselib_store = 'OPENMP'
- )
- except self.errors.ConfigurationError:
- pass
- else:
- break
- else:
- self.fatal('Could not find OpenMP')
- @conf
- def check_gfortran_o_space(self):
- if self.env.FC_NAME != 'GFORTRAN' or int(self.env.FC_VERSION[0]) > 4:
- # This is for old compilers and only for gfortran.
- # No idea how other implementations handle this. Be safe and bail out.
- return
- self.env.stash()
- self.env.FCLNK_TGT_F = ['-o', '']
- try:
- self.check_fc(msg='Checking if the -o link must be split from arguments', fragment=FC_FRAGMENT, features='fc fcshlib')
- except self.errors.ConfigurationError:
- self.env.revert()
- else:
- self.env.commit()
|