gnu.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. from __future__ import division, absolute_import, print_function
  2. import re
  3. import os
  4. import sys
  5. import warnings
  6. import platform
  7. import tempfile
  8. import hashlib
  9. import base64
  10. import subprocess
  11. from subprocess import Popen, PIPE, STDOUT
  12. from numpy.distutils.exec_command import filepath_from_subprocess_output
  13. from numpy.distutils.fcompiler import FCompiler
  14. from numpy.distutils.compat import get_exception
  15. from numpy.distutils.system_info import system_info
  16. compilers = ['GnuFCompiler', 'Gnu95FCompiler']
  17. TARGET_R = re.compile(r"Target: ([a-zA-Z0-9_\-]*)")
  18. # XXX: handle cross compilation
  19. def is_win64():
  20. return sys.platform == "win32" and platform.architecture()[0] == "64bit"
  21. if is_win64():
  22. #_EXTRAFLAGS = ["-fno-leading-underscore"]
  23. _EXTRAFLAGS = []
  24. else:
  25. _EXTRAFLAGS = []
  26. class GnuFCompiler(FCompiler):
  27. compiler_type = 'gnu'
  28. compiler_aliases = ('g77', )
  29. description = 'GNU Fortran 77 compiler'
  30. def gnu_version_match(self, version_string):
  31. """Handle the different versions of GNU fortran compilers"""
  32. # Strip warning(s) that may be emitted by gfortran
  33. while version_string.startswith('gfortran: warning'):
  34. version_string = version_string[version_string.find('\n') + 1:]
  35. # Gfortran versions from after 2010 will output a simple string
  36. # (usually "x.y", "x.y.z" or "x.y.z-q") for ``-dumpversion``; older
  37. # gfortrans may still return long version strings (``-dumpversion`` was
  38. # an alias for ``--version``)
  39. if len(version_string) <= 20:
  40. # Try to find a valid version string
  41. m = re.search(r'([0-9.]+)', version_string)
  42. if m:
  43. # g77 provides a longer version string that starts with GNU
  44. # Fortran
  45. if version_string.startswith('GNU Fortran'):
  46. return ('g77', m.group(1))
  47. # gfortran only outputs a version string such as #.#.#, so check
  48. # if the match is at the start of the string
  49. elif m.start() == 0:
  50. return ('gfortran', m.group(1))
  51. else:
  52. # Output probably from --version, try harder:
  53. m = re.search(r'GNU Fortran\s+95.*?([0-9-.]+)', version_string)
  54. if m:
  55. return ('gfortran', m.group(1))
  56. m = re.search(
  57. r'GNU Fortran.*?\-?([0-9-.]+\.[0-9-.]+)', version_string)
  58. if m:
  59. v = m.group(1)
  60. if v.startswith('0') or v.startswith('2') or v.startswith('3'):
  61. # the '0' is for early g77's
  62. return ('g77', v)
  63. else:
  64. # at some point in the 4.x series, the ' 95' was dropped
  65. # from the version string
  66. return ('gfortran', v)
  67. # If still nothing, raise an error to make the problem easy to find.
  68. err = 'A valid Fortran version was not found in this string:\n'
  69. raise ValueError(err + version_string)
  70. def version_match(self, version_string):
  71. v = self.gnu_version_match(version_string)
  72. if not v or v[0] != 'g77':
  73. return None
  74. return v[1]
  75. possible_executables = ['g77', 'f77']
  76. executables = {
  77. 'version_cmd' : [None, "-dumpversion"],
  78. 'compiler_f77' : [None, "-g", "-Wall", "-fno-second-underscore"],
  79. 'compiler_f90' : None, # Use --fcompiler=gnu95 for f90 codes
  80. 'compiler_fix' : None,
  81. 'linker_so' : [None, "-g", "-Wall"],
  82. 'archiver' : ["ar", "-cr"],
  83. 'ranlib' : ["ranlib"],
  84. 'linker_exe' : [None, "-g", "-Wall"]
  85. }
  86. module_dir_switch = None
  87. module_include_switch = None
  88. # Cygwin: f771: warning: -fPIC ignored for target (all code is
  89. # position independent)
  90. if os.name != 'nt' and sys.platform != 'cygwin':
  91. pic_flags = ['-fPIC']
  92. # use -mno-cygwin for g77 when Python is not Cygwin-Python
  93. if sys.platform == 'win32':
  94. for key in ['version_cmd', 'compiler_f77', 'linker_so', 'linker_exe']:
  95. executables[key].append('-mno-cygwin')
  96. g2c = 'g2c'
  97. suggested_f90_compiler = 'gnu95'
  98. def get_flags_linker_so(self):
  99. opt = self.linker_so[1:]
  100. if sys.platform == 'darwin':
  101. target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', None)
  102. # If MACOSX_DEPLOYMENT_TARGET is set, we simply trust the value
  103. # and leave it alone. But, distutils will complain if the
  104. # environment's value is different from the one in the Python
  105. # Makefile used to build Python. We let disutils handle this
  106. # error checking.
  107. if not target:
  108. # If MACOSX_DEPLOYMENT_TARGET is not set in the environment,
  109. # we try to get it first from the Python Makefile and then we
  110. # fall back to setting it to 10.3 to maximize the set of
  111. # versions we can work with. This is a reasonable default
  112. # even when using the official Python dist and those derived
  113. # from it.
  114. import distutils.sysconfig as sc
  115. g = {}
  116. try:
  117. get_makefile_filename = sc.get_makefile_filename
  118. except AttributeError:
  119. pass # i.e. PyPy
  120. else:
  121. filename = get_makefile_filename()
  122. sc.parse_makefile(filename, g)
  123. target = g.get('MACOSX_DEPLOYMENT_TARGET', '10.3')
  124. os.environ['MACOSX_DEPLOYMENT_TARGET'] = target
  125. if target == '10.3':
  126. s = 'Env. variable MACOSX_DEPLOYMENT_TARGET set to 10.3'
  127. warnings.warn(s, stacklevel=2)
  128. opt.extend(['-undefined', 'dynamic_lookup', '-bundle'])
  129. else:
  130. opt.append("-shared")
  131. if sys.platform.startswith('sunos'):
  132. # SunOS often has dynamically loaded symbols defined in the
  133. # static library libg2c.a The linker doesn't like this. To
  134. # ignore the problem, use the -mimpure-text flag. It isn't
  135. # the safest thing, but seems to work. 'man gcc' says:
  136. # ".. Instead of using -mimpure-text, you should compile all
  137. # source code with -fpic or -fPIC."
  138. opt.append('-mimpure-text')
  139. return opt
  140. def get_libgcc_dir(self):
  141. try:
  142. output = subprocess.check_output(self.compiler_f77 +
  143. ['-print-libgcc-file-name'])
  144. except (OSError, subprocess.CalledProcessError):
  145. pass
  146. else:
  147. output = filepath_from_subprocess_output(output)
  148. return os.path.dirname(output)
  149. return None
  150. def get_libgfortran_dir(self):
  151. if sys.platform[:5] == 'linux':
  152. libgfortran_name = 'libgfortran.so'
  153. elif sys.platform == 'darwin':
  154. libgfortran_name = 'libgfortran.dylib'
  155. else:
  156. libgfortran_name = None
  157. libgfortran_dir = None
  158. if libgfortran_name:
  159. find_lib_arg = ['-print-file-name={0}'.format(libgfortran_name)]
  160. try:
  161. output = subprocess.check_output(
  162. self.compiler_f77 + find_lib_arg)
  163. except (OSError, subprocess.CalledProcessError):
  164. pass
  165. else:
  166. output = filepath_from_subprocess_output(output)
  167. libgfortran_dir = os.path.dirname(output)
  168. return libgfortran_dir
  169. def get_library_dirs(self):
  170. opt = []
  171. if sys.platform[:5] != 'linux':
  172. d = self.get_libgcc_dir()
  173. if d:
  174. # if windows and not cygwin, libg2c lies in a different folder
  175. if sys.platform == 'win32' and not d.startswith('/usr/lib'):
  176. d = os.path.normpath(d)
  177. path = os.path.join(d, "lib%s.a" % self.g2c)
  178. if not os.path.exists(path):
  179. root = os.path.join(d, *((os.pardir, ) * 4))
  180. d2 = os.path.abspath(os.path.join(root, 'lib'))
  181. path = os.path.join(d2, "lib%s.a" % self.g2c)
  182. if os.path.exists(path):
  183. opt.append(d2)
  184. opt.append(d)
  185. # For Macports / Linux, libgfortran and libgcc are not co-located
  186. lib_gfortran_dir = self.get_libgfortran_dir()
  187. if lib_gfortran_dir:
  188. opt.append(lib_gfortran_dir)
  189. return opt
  190. def get_libraries(self):
  191. opt = []
  192. d = self.get_libgcc_dir()
  193. if d is not None:
  194. g2c = self.g2c + '-pic'
  195. f = self.static_lib_format % (g2c, self.static_lib_extension)
  196. if not os.path.isfile(os.path.join(d, f)):
  197. g2c = self.g2c
  198. else:
  199. g2c = self.g2c
  200. if g2c is not None:
  201. opt.append(g2c)
  202. c_compiler = self.c_compiler
  203. if sys.platform == 'win32' and c_compiler and \
  204. c_compiler.compiler_type == 'msvc':
  205. opt.append('gcc')
  206. if sys.platform == 'darwin':
  207. opt.append('cc_dynamic')
  208. return opt
  209. def get_flags_debug(self):
  210. return ['-g']
  211. def get_flags_opt(self):
  212. v = self.get_version()
  213. if v and v <= '3.3.3':
  214. # With this compiler version building Fortran BLAS/LAPACK
  215. # with -O3 caused failures in lib.lapack heevr,syevr tests.
  216. opt = ['-O2']
  217. else:
  218. opt = ['-O3']
  219. opt.append('-funroll-loops')
  220. return opt
  221. def _c_arch_flags(self):
  222. """ Return detected arch flags from CFLAGS """
  223. from distutils import sysconfig
  224. try:
  225. cflags = sysconfig.get_config_vars()['CFLAGS']
  226. except KeyError:
  227. return []
  228. arch_re = re.compile(r"-arch\s+(\w+)")
  229. arch_flags = []
  230. for arch in arch_re.findall(cflags):
  231. arch_flags += ['-arch', arch]
  232. return arch_flags
  233. def get_flags_arch(self):
  234. return []
  235. def runtime_library_dir_option(self, dir):
  236. if sys.platform[:3] == 'aix' or sys.platform == 'win32':
  237. # Linux/Solaris/Unix support RPATH, Windows and AIX do not
  238. raise NotImplementedError
  239. # TODO: could use -Xlinker here, if it's supported
  240. assert "," not in dir
  241. sep = ',' if sys.platform == 'darwin' else '='
  242. return '-Wl,-rpath%s%s' % (sep, dir)
  243. class Gnu95FCompiler(GnuFCompiler):
  244. compiler_type = 'gnu95'
  245. compiler_aliases = ('gfortran', )
  246. description = 'GNU Fortran 95 compiler'
  247. def version_match(self, version_string):
  248. v = self.gnu_version_match(version_string)
  249. if not v or v[0] != 'gfortran':
  250. return None
  251. v = v[1]
  252. if v >= '4.':
  253. # gcc-4 series releases do not support -mno-cygwin option
  254. pass
  255. else:
  256. # use -mno-cygwin flag for gfortran when Python is not
  257. # Cygwin-Python
  258. if sys.platform == 'win32':
  259. for key in [
  260. 'version_cmd', 'compiler_f77', 'compiler_f90',
  261. 'compiler_fix', 'linker_so', 'linker_exe'
  262. ]:
  263. self.executables[key].append('-mno-cygwin')
  264. return v
  265. possible_executables = ['gfortran', 'f95']
  266. executables = {
  267. 'version_cmd' : ["<F90>", "-dumpversion"],
  268. 'compiler_f77' : [None, "-Wall", "-g", "-ffixed-form",
  269. "-fno-second-underscore"] + _EXTRAFLAGS,
  270. 'compiler_f90' : [None, "-Wall", "-g",
  271. "-fno-second-underscore"] + _EXTRAFLAGS,
  272. 'compiler_fix' : [None, "-Wall", "-g","-ffixed-form",
  273. "-fno-second-underscore"] + _EXTRAFLAGS,
  274. 'linker_so' : ["<F90>", "-Wall", "-g"],
  275. 'archiver' : ["ar", "-cr"],
  276. 'ranlib' : ["ranlib"],
  277. 'linker_exe' : [None, "-Wall"]
  278. }
  279. module_dir_switch = '-J'
  280. module_include_switch = '-I'
  281. if sys.platform[:3] == 'aix':
  282. executables['linker_so'].append('-lpthread')
  283. if platform.architecture()[0][:2] == '64':
  284. for key in ['compiler_f77', 'compiler_f90','compiler_fix','linker_so', 'linker_exe']:
  285. executables[key].append('-maix64')
  286. g2c = 'gfortran'
  287. def _universal_flags(self, cmd):
  288. """Return a list of -arch flags for every supported architecture."""
  289. if not sys.platform == 'darwin':
  290. return []
  291. arch_flags = []
  292. # get arches the C compiler gets.
  293. c_archs = self._c_arch_flags()
  294. if "i386" in c_archs:
  295. c_archs[c_archs.index("i386")] = "i686"
  296. # check the arches the Fortran compiler supports, and compare with
  297. # arch flags from C compiler
  298. for arch in ["ppc", "i686", "x86_64", "ppc64"]:
  299. if _can_target(cmd, arch) and arch in c_archs:
  300. arch_flags.extend(["-arch", arch])
  301. return arch_flags
  302. def get_flags(self):
  303. flags = GnuFCompiler.get_flags(self)
  304. arch_flags = self._universal_flags(self.compiler_f90)
  305. if arch_flags:
  306. flags[:0] = arch_flags
  307. return flags
  308. def get_flags_linker_so(self):
  309. flags = GnuFCompiler.get_flags_linker_so(self)
  310. arch_flags = self._universal_flags(self.linker_so)
  311. if arch_flags:
  312. flags[:0] = arch_flags
  313. return flags
  314. def get_library_dirs(self):
  315. opt = GnuFCompiler.get_library_dirs(self)
  316. if sys.platform == 'win32':
  317. c_compiler = self.c_compiler
  318. if c_compiler and c_compiler.compiler_type == "msvc":
  319. target = self.get_target()
  320. if target:
  321. d = os.path.normpath(self.get_libgcc_dir())
  322. root = os.path.join(d, *((os.pardir, ) * 4))
  323. path = os.path.join(root, "lib")
  324. mingwdir = os.path.normpath(path)
  325. if os.path.exists(os.path.join(mingwdir, "libmingwex.a")):
  326. opt.append(mingwdir)
  327. # For Macports / Linux, libgfortran and libgcc are not co-located
  328. lib_gfortran_dir = self.get_libgfortran_dir()
  329. if lib_gfortran_dir:
  330. opt.append(lib_gfortran_dir)
  331. return opt
  332. def get_libraries(self):
  333. opt = GnuFCompiler.get_libraries(self)
  334. if sys.platform == 'darwin':
  335. opt.remove('cc_dynamic')
  336. if sys.platform == 'win32':
  337. c_compiler = self.c_compiler
  338. if c_compiler and c_compiler.compiler_type == "msvc":
  339. if "gcc" in opt:
  340. i = opt.index("gcc")
  341. opt.insert(i + 1, "mingwex")
  342. opt.insert(i + 1, "mingw32")
  343. c_compiler = self.c_compiler
  344. if c_compiler and c_compiler.compiler_type == "msvc":
  345. return []
  346. else:
  347. pass
  348. return opt
  349. def get_target(self):
  350. try:
  351. output = subprocess.check_output(self.compiler_f77 + ['-v'])
  352. except (OSError, subprocess.CalledProcessError):
  353. pass
  354. else:
  355. output = filepath_from_subprocess_output(output)
  356. m = TARGET_R.search(output)
  357. if m:
  358. return m.group(1)
  359. return ""
  360. def _hash_files(self, filenames):
  361. h = hashlib.sha1()
  362. for fn in filenames:
  363. with open(fn, 'rb') as f:
  364. while True:
  365. block = f.read(131072)
  366. if not block:
  367. break
  368. h.update(block)
  369. text = base64.b32encode(h.digest())
  370. if sys.version_info[0] >= 3:
  371. text = text.decode('ascii')
  372. return text.rstrip('=')
  373. def _link_wrapper_lib(self, objects, output_dir, extra_dll_dir,
  374. chained_dlls, is_archive):
  375. """Create a wrapper shared library for the given objects
  376. Return an MSVC-compatible lib
  377. """
  378. c_compiler = self.c_compiler
  379. if c_compiler.compiler_type != "msvc":
  380. raise ValueError("This method only supports MSVC")
  381. object_hash = self._hash_files(list(objects) + list(chained_dlls))
  382. if is_win64():
  383. tag = 'win_amd64'
  384. else:
  385. tag = 'win32'
  386. basename = 'lib' + os.path.splitext(
  387. os.path.basename(objects[0]))[0][:8]
  388. root_name = basename + '.' + object_hash + '.gfortran-' + tag
  389. dll_name = root_name + '.dll'
  390. def_name = root_name + '.def'
  391. lib_name = root_name + '.lib'
  392. dll_path = os.path.join(extra_dll_dir, dll_name)
  393. def_path = os.path.join(output_dir, def_name)
  394. lib_path = os.path.join(output_dir, lib_name)
  395. if os.path.isfile(lib_path):
  396. # Nothing to do
  397. return lib_path, dll_path
  398. if is_archive:
  399. objects = (["-Wl,--whole-archive"] + list(objects) +
  400. ["-Wl,--no-whole-archive"])
  401. self.link_shared_object(
  402. objects,
  403. dll_name,
  404. output_dir=extra_dll_dir,
  405. extra_postargs=list(chained_dlls) + [
  406. '-Wl,--allow-multiple-definition',
  407. '-Wl,--output-def,' + def_path,
  408. '-Wl,--export-all-symbols',
  409. '-Wl,--enable-auto-import',
  410. '-static',
  411. '-mlong-double-64',
  412. ])
  413. # No PowerPC!
  414. if is_win64():
  415. specifier = '/MACHINE:X64'
  416. else:
  417. specifier = '/MACHINE:X86'
  418. # MSVC specific code
  419. lib_args = ['/def:' + def_path, '/OUT:' + lib_path, specifier]
  420. if not c_compiler.initialized:
  421. c_compiler.initialize()
  422. c_compiler.spawn([c_compiler.lib] + lib_args)
  423. return lib_path, dll_path
  424. def can_ccompiler_link(self, compiler):
  425. # MSVC cannot link objects compiled by GNU fortran
  426. return compiler.compiler_type not in ("msvc", )
  427. def wrap_unlinkable_objects(self, objects, output_dir, extra_dll_dir):
  428. """
  429. Convert a set of object files that are not compatible with the default
  430. linker, to a file that is compatible.
  431. """
  432. if self.c_compiler.compiler_type == "msvc":
  433. # Compile a DLL and return the lib for the DLL as
  434. # the object. Also keep track of previous DLLs that
  435. # we have compiled so that we can link against them.
  436. # If there are .a archives, assume they are self-contained
  437. # static libraries, and build separate DLLs for each
  438. archives = []
  439. plain_objects = []
  440. for obj in objects:
  441. if obj.lower().endswith('.a'):
  442. archives.append(obj)
  443. else:
  444. plain_objects.append(obj)
  445. chained_libs = []
  446. chained_dlls = []
  447. for archive in archives[::-1]:
  448. lib, dll = self._link_wrapper_lib(
  449. [archive],
  450. output_dir,
  451. extra_dll_dir,
  452. chained_dlls=chained_dlls,
  453. is_archive=True)
  454. chained_libs.insert(0, lib)
  455. chained_dlls.insert(0, dll)
  456. if not plain_objects:
  457. return chained_libs
  458. lib, dll = self._link_wrapper_lib(
  459. plain_objects,
  460. output_dir,
  461. extra_dll_dir,
  462. chained_dlls=chained_dlls,
  463. is_archive=False)
  464. return [lib] + chained_libs
  465. else:
  466. raise ValueError("Unsupported C compiler")
  467. def _can_target(cmd, arch):
  468. """Return true if the architecture supports the -arch flag"""
  469. newcmd = cmd[:]
  470. fid, filename = tempfile.mkstemp(suffix=".f")
  471. os.close(fid)
  472. try:
  473. d = os.path.dirname(filename)
  474. output = os.path.splitext(filename)[0] + ".o"
  475. try:
  476. newcmd.extend(["-arch", arch, "-c", filename])
  477. p = Popen(newcmd, stderr=STDOUT, stdout=PIPE, cwd=d)
  478. p.communicate()
  479. return p.returncode == 0
  480. finally:
  481. if os.path.exists(output):
  482. os.remove(output)
  483. finally:
  484. os.remove(filename)
  485. return False
  486. if __name__ == '__main__':
  487. from distutils import log
  488. from numpy.distutils import customized_fcompiler
  489. log.set_verbosity(2)
  490. print(customized_fcompiler('gnu').get_version())
  491. try:
  492. print(customized_fcompiler('g95').get_version())
  493. except Exception:
  494. print(get_exception())