jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
13KB

  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. # DC 2008
  4. # Thomas Nagy 2010 (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. from waflib import Utils
  12. FC_FRAGMENT = ' program main\n end program main\n'
  13. FC_FRAGMENT2 = ' PROGRAM MAIN\n END\n' # what's the actual difference between these?
  14. @conf
  15. def fc_flags(conf):
  16. """
  17. Define common fortran configuration flags and file extensions
  18. """
  19. v = conf.env
  20. v['FC_SRC_F'] = []
  21. v['FC_TGT_F'] = ['-c', '-o']
  22. v['FCINCPATH_ST'] = '-I%s'
  23. v['FCDEFINES_ST'] = '-D%s'
  24. if not v['LINK_FC']: 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. Add FCFLAGS / LDFLAGS / LINKFLAGS from os.environ to conf.env
  42. """
  43. conf.add_os_flags('FCFLAGS')
  44. conf.add_os_flags('LINKFLAGS')
  45. conf.add_os_flags('LDFLAGS')
  46. @conf
  47. def check_fortran(self, *k, **kw):
  48. """See if the fortran compiler works by compiling a simple fortran program"""
  49. self.check_cc(
  50. fragment = FC_FRAGMENT,
  51. compile_filename = 'test.f',
  52. features = 'fc fcprogram',
  53. msg = 'Compiling a simple fortran app')
  54. @conf
  55. def check_fc(self, *k, **kw):
  56. """
  57. Same as :py:func:`waflib.Tools.c_config.check` but default to the *Fortran* programming language
  58. (Overriding the C defaults in :py:func:`waflib.Tools.c_config.validate_c` here)
  59. """
  60. kw['compiler'] = 'fc'
  61. if not 'compile_mode' in kw:
  62. kw['compile_mode'] = 'fc'
  63. if not 'type' in kw:
  64. kw['type'] = 'fcprogram'
  65. if not 'compile_filename' in kw:
  66. kw['compile_filename'] = 'test.f90'
  67. if not 'code' in kw:
  68. kw['code'] = FC_FRAGMENT
  69. return self.check(*k, **kw)
  70. # ------------------------------------------------------------------------
  71. # --- These are the default platform modifiers, refactored here for
  72. # convenience. gfortran and g95 have much overlap.
  73. # ------------------------------------------------------------------------
  74. @conf
  75. def fortran_modifier_darwin(conf):
  76. """
  77. Define fortran flags and extensions for the OSX systems
  78. """
  79. v = conf.env
  80. v['FCFLAGS_fcshlib'] = ['-fPIC']
  81. v['LINKFLAGS_fcshlib'] = ['-dynamiclib', '-Wl,-compatibility_version,1', '-Wl,-current_version,1']
  82. v['fcshlib_PATTERN'] = 'lib%s.dylib'
  83. v['FRAMEWORKPATH_ST'] = '-F%s'
  84. v['FRAMEWORK_ST'] = '-framework %s'
  85. v['LINKFLAGS_fcstlib'] = []
  86. v['FCSHLIB_MARKER'] = ''
  87. v['FCSTLIB_MARKER'] = ''
  88. v['SONAME_ST'] = ''
  89. @conf
  90. def fortran_modifier_win32(conf):
  91. """Define fortran flags for the windows platforms"""
  92. v = conf.env
  93. v['fcprogram_PATTERN'] = v['fcprogram_test_PATTERN'] = '%s.exe'
  94. v['fcshlib_PATTERN'] = '%s.dll'
  95. v['implib_PATTERN'] = 'lib%s.dll.a'
  96. v['IMPLIB_ST'] = '-Wl,--out-implib,%s'
  97. v['FCFLAGS_fcshlib'] = []
  98. v.append_value('FCFLAGS_fcshlib', ['-DDLL_EXPORT']) # TODO adding nonstandard defines like this DLL_EXPORT is not a good idea
  99. # Auto-import is enabled by default even without this option,
  100. # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
  101. # that the linker emits otherwise.
  102. v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
  103. @conf
  104. def fortran_modifier_cygwin(conf):
  105. """Define fortran flags for use on cygwin"""
  106. fortran_modifier_win32(conf)
  107. v = conf.env
  108. v['fcshlib_PATTERN'] = 'cyg%s.dll'
  109. v.append_value('LINKFLAGS_fcshlib', ['-Wl,--enable-auto-image-base'])
  110. v['FCFLAGS_fcshlib'] = []
  111. # ------------------------------------------------------------------------
  112. @conf
  113. def check_fortran_dummy_main(self, *k, **kw):
  114. """
  115. Guess if a main function is needed by compiling a code snippet with
  116. the C compiler and link with the Fortran compiler
  117. TODO: (DC)
  118. - handling dialects (F77, F90, etc... -> needs core support first)
  119. - fix dummy main check (AC_FC_DUMMY_MAIN vs AC_FC_MAIN)
  120. TODO: what does the above mean? (ita)
  121. """
  122. if not self.env.CC:
  123. self.fatal('A c compiler is required for check_fortran_dummy_main')
  124. lst = ['MAIN__', '__MAIN', '_MAIN', 'MAIN_', 'MAIN']
  125. lst.extend([m.lower() for m in lst])
  126. lst.append('')
  127. self.start_msg('Detecting whether we need a dummy main')
  128. for main in lst:
  129. kw['fortran_main'] = main
  130. try:
  131. self.check_cc(
  132. fragment = 'int %s() { return 0; }\n' % (main or 'test'),
  133. features = 'c fcprogram',
  134. mandatory = True
  135. )
  136. if not main:
  137. self.env.FC_MAIN = -1
  138. self.end_msg('no')
  139. else:
  140. self.env.FC_MAIN = main
  141. self.end_msg('yes %s' % main)
  142. break
  143. except self.errors.ConfigurationError:
  144. pass
  145. else:
  146. self.end_msg('not found')
  147. self.fatal('could not detect whether fortran requires a dummy main, see the config.log')
  148. # ------------------------------------------------------------------------
  149. GCC_DRIVER_LINE = re.compile('^Driving:')
  150. POSIX_STATIC_EXT = re.compile('\S+\.a')
  151. POSIX_LIB_FLAGS = re.compile('-l\S+')
  152. @conf
  153. def is_link_verbose(self, txt):
  154. """Return True if 'useful' link options can be found in txt"""
  155. assert isinstance(txt, str)
  156. for line in txt.splitlines():
  157. if not GCC_DRIVER_LINE.search(line):
  158. if POSIX_STATIC_EXT.search(line) or POSIX_LIB_FLAGS.search(line):
  159. return True
  160. return False
  161. @conf
  162. def check_fortran_verbose_flag(self, *k, **kw):
  163. """
  164. Check what kind of verbose (-v) flag works, then set it to env.FC_VERBOSE_FLAG
  165. """
  166. self.start_msg('fortran link verbose flag')
  167. for x in ('-v', '--verbose', '-verbose', '-V'):
  168. try:
  169. self.check_cc(
  170. features = 'fc fcprogram_test',
  171. fragment = FC_FRAGMENT2,
  172. compile_filename = 'test.f',
  173. linkflags = [x],
  174. mandatory=True
  175. )
  176. except self.errors.ConfigurationError:
  177. pass
  178. else:
  179. # output is on stderr or stdout (for xlf)
  180. if self.is_link_verbose(self.test_bld.err) or self.is_link_verbose(self.test_bld.out):
  181. self.end_msg(x)
  182. break
  183. else:
  184. self.end_msg('failure')
  185. self.fatal('Could not obtain the fortran link verbose flag (see config.log)')
  186. self.env.FC_VERBOSE_FLAG = x
  187. return x
  188. # ------------------------------------------------------------------------
  189. # linkflags which match those are ignored
  190. LINKFLAGS_IGNORED = [r'-lang*', r'-lcrt[a-zA-Z0-9\.]*\.o', r'-lc$', r'-lSystem', r'-libmil', r'-LIST:*', r'-LNO:*']
  191. if os.name == 'nt':
  192. LINKFLAGS_IGNORED.extend([r'-lfrt*', r'-luser32', r'-lkernel32', r'-ladvapi32', r'-lmsvcrt', r'-lshell32', r'-lmingw', r'-lmoldname'])
  193. else:
  194. LINKFLAGS_IGNORED.append(r'-lgcc*')
  195. RLINKFLAGS_IGNORED = [re.compile(f) for f in LINKFLAGS_IGNORED]
  196. def _match_ignore(line):
  197. """Returns True if the line should be ignored (fortran test for verbosity)."""
  198. for i in RLINKFLAGS_IGNORED:
  199. if i.match(line):
  200. return True
  201. return False
  202. def parse_fortran_link(lines):
  203. """Given the output of verbose link of Fortran compiler, this returns a
  204. list of flags necessary for linking using the standard linker."""
  205. # TODO: On windows ?
  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. Obtain 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. TODO a bit redundant, can be removed anytime
  289. """
  290. if stdin:
  291. stdin = Utils.subprocess.PIPE
  292. else:
  293. stdin = None
  294. env = conf.env.env or None
  295. try:
  296. p = Utils.subprocess.Popen(cmd, stdin=stdin, stdout=Utils.subprocess.PIPE, stderr=Utils.subprocess.PIPE, env=env)
  297. if stdin:
  298. p.stdin.write('\n'.encode())
  299. out, err = p.communicate()
  300. except Exception:
  301. conf.fatal('could not determine the compiler version %r' % cmd)
  302. if not isinstance(out, str):
  303. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  304. if not isinstance(err, str):
  305. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  306. return (out, err)
  307. # ------------------------------------------------------------------------
  308. ROUTINES_CODE = """\
  309. subroutine foobar()
  310. return
  311. end
  312. subroutine foo_bar()
  313. return
  314. end
  315. """
  316. MAIN_CODE = """
  317. void %(dummy_func_nounder)s(void);
  318. void %(dummy_func_under)s(void);
  319. int %(main_func_name)s() {
  320. %(dummy_func_nounder)s();
  321. %(dummy_func_under)s();
  322. return 0;
  323. }
  324. """
  325. @feature('link_main_routines_func')
  326. @before_method('process_source')
  327. def link_main_routines_tg_method(self):
  328. """
  329. The configuration test declares a unique task generator,
  330. so we create other task generators from there for fortran link tests
  331. """
  332. def write_test_file(task):
  333. task.outputs[0].write(task.generator.code)
  334. bld = self.bld
  335. bld(rule=write_test_file, target='main.c', code=MAIN_CODE % self.__dict__)
  336. bld(rule=write_test_file, target='test.f', code=ROUTINES_CODE)
  337. bld(features='fc fcstlib', source='test.f', target='test')
  338. bld(features='c fcprogram', source='main.c', target='app', use='test')
  339. def mangling_schemes():
  340. """
  341. Generate triplets for use with mangle_name
  342. (used in check_fortran_mangling)
  343. the order is tuned for gfortan
  344. """
  345. for u in ('_', ''):
  346. for du in ('', '_'):
  347. for c in ("lower", "upper"):
  348. yield (u, du, c)
  349. def mangle_name(u, du, c, name):
  350. """Mangle a name from a triplet (used in check_fortran_mangling)"""
  351. return getattr(name, c)() + u + (name.find('_') != -1 and du or '')
  352. @conf
  353. def check_fortran_mangling(self, *k, **kw):
  354. """
  355. Detect the mangling scheme, sets FORTRAN_MANGLING to the triplet found
  356. This test will compile a fortran static library, then link a c app against it
  357. """
  358. if not self.env.CC:
  359. self.fatal('A c compiler is required for link_main_routines')
  360. if not self.env.FC:
  361. self.fatal('A fortran compiler is required for link_main_routines')
  362. if not self.env.FC_MAIN:
  363. self.fatal('Checking for mangling requires self.env.FC_MAIN (execute "check_fortran_dummy_main" first?)')
  364. self.start_msg('Getting fortran mangling scheme')
  365. for (u, du, c) in mangling_schemes():
  366. try:
  367. self.check_cc(
  368. compile_filename = [],
  369. features = 'link_main_routines_func',
  370. msg = 'nomsg',
  371. errmsg = 'nomsg',
  372. mandatory=True,
  373. dummy_func_nounder = mangle_name(u, du, c, "foobar"),
  374. dummy_func_under = mangle_name(u, du, c, "foo_bar"),
  375. main_func_name = self.env.FC_MAIN
  376. )
  377. except self.errors.ConfigurationError:
  378. pass
  379. else:
  380. self.end_msg("ok ('%s', '%s', '%s-case')" % (u, du, c))
  381. self.env.FORTRAN_MANGLING = (u, du, c)
  382. break
  383. else:
  384. self.end_msg(False)
  385. self.fatal('mangler not found')
  386. return (u, du, c)
  387. @feature('pyext')
  388. @before_method('propagate_uselib_vars', 'apply_link')
  389. def set_lib_pat(self):
  390. """Set the fortran flags for linking with the python library"""
  391. self.env['fcshlib_PATTERN'] = self.env['pyext_PATTERN']
  392. @conf
  393. def detect_openmp(self):
  394. for x in ('-fopenmp','-openmp','-mp','-xopenmp','-omp','-qsmp=omp'):
  395. try:
  396. self.check_fc(
  397. msg='Checking for OpenMP flag %s' % x,
  398. fragment='program main\n call omp_get_num_threads()\nend program main',
  399. fcflags=x,
  400. linkflags=x,
  401. uselib_store='OPENMP'
  402. )
  403. except self.errors.ConfigurationError:
  404. pass
  405. else:
  406. break
  407. else:
  408. self.fatal('Could not find OpenMP')