fc_config.py 14 KB

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