test_system_info.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import os
  2. import shutil
  3. import pytest
  4. from tempfile import mkstemp, mkdtemp
  5. from subprocess import Popen, PIPE
  6. from distutils.errors import DistutilsError
  7. from numpy.testing import assert_, assert_equal, assert_raises
  8. from numpy.distutils import ccompiler, customized_ccompiler
  9. from numpy.distutils.system_info import system_info, ConfigParser, mkl_info
  10. from numpy.distutils.system_info import AliasedOptionError
  11. from numpy.distutils.system_info import default_lib_dirs, default_include_dirs
  12. from numpy.distutils import _shell_utils
  13. def get_class(name, notfound_action=1):
  14. """
  15. notfound_action:
  16. 0 - do nothing
  17. 1 - display warning message
  18. 2 - raise error
  19. """
  20. cl = {'temp1': Temp1Info,
  21. 'temp2': Temp2Info,
  22. 'duplicate_options': DuplicateOptionInfo,
  23. }.get(name.lower(), _system_info)
  24. return cl()
  25. simple_site = """
  26. [ALL]
  27. library_dirs = {dir1:s}{pathsep:s}{dir2:s}
  28. libraries = {lib1:s},{lib2:s}
  29. extra_compile_args = -I/fake/directory -I"/path with/spaces" -Os
  30. runtime_library_dirs = {dir1:s}
  31. [temp1]
  32. library_dirs = {dir1:s}
  33. libraries = {lib1:s}
  34. runtime_library_dirs = {dir1:s}
  35. [temp2]
  36. library_dirs = {dir2:s}
  37. libraries = {lib2:s}
  38. extra_link_args = -Wl,-rpath={lib2_escaped:s}
  39. rpath = {dir2:s}
  40. [duplicate_options]
  41. mylib_libs = {lib1:s}
  42. libraries = {lib2:s}
  43. """
  44. site_cfg = simple_site
  45. fakelib_c_text = """
  46. /* This file is generated from numpy/distutils/testing/test_system_info.py */
  47. #include<stdio.h>
  48. void foo(void) {
  49. printf("Hello foo");
  50. }
  51. void bar(void) {
  52. printf("Hello bar");
  53. }
  54. """
  55. def have_compiler():
  56. """ Return True if there appears to be an executable compiler
  57. """
  58. compiler = customized_ccompiler()
  59. try:
  60. cmd = compiler.compiler # Unix compilers
  61. except AttributeError:
  62. try:
  63. if not compiler.initialized:
  64. compiler.initialize() # MSVC is different
  65. except (DistutilsError, ValueError):
  66. return False
  67. cmd = [compiler.cc]
  68. try:
  69. p = Popen(cmd, stdout=PIPE, stderr=PIPE)
  70. p.stdout.close()
  71. p.stderr.close()
  72. p.wait()
  73. except OSError:
  74. return False
  75. return True
  76. HAVE_COMPILER = have_compiler()
  77. class _system_info(system_info):
  78. def __init__(self,
  79. default_lib_dirs=default_lib_dirs,
  80. default_include_dirs=default_include_dirs,
  81. verbosity=1,
  82. ):
  83. self.__class__.info = {}
  84. self.local_prefixes = []
  85. defaults = {'library_dirs': '',
  86. 'include_dirs': '',
  87. 'runtime_library_dirs': '',
  88. 'rpath': '',
  89. 'src_dirs': '',
  90. 'search_static_first': "0",
  91. 'extra_compile_args': '',
  92. 'extra_link_args': ''}
  93. self.cp = ConfigParser(defaults)
  94. # We have to parse the config files afterwards
  95. # to have a consistent temporary filepath
  96. def _check_libs(self, lib_dirs, libs, opt_libs, exts):
  97. """Override _check_libs to return with all dirs """
  98. info = {'libraries': libs, 'library_dirs': lib_dirs}
  99. return info
  100. class Temp1Info(_system_info):
  101. """For testing purposes"""
  102. section = 'temp1'
  103. class Temp2Info(_system_info):
  104. """For testing purposes"""
  105. section = 'temp2'
  106. class DuplicateOptionInfo(_system_info):
  107. """For testing purposes"""
  108. section = 'duplicate_options'
  109. class TestSystemInfoReading:
  110. def setup_method(self):
  111. """ Create the libraries """
  112. # Create 2 sources and 2 libraries
  113. self._dir1 = mkdtemp()
  114. self._src1 = os.path.join(self._dir1, 'foo.c')
  115. self._lib1 = os.path.join(self._dir1, 'libfoo.so')
  116. self._dir2 = mkdtemp()
  117. self._src2 = os.path.join(self._dir2, 'bar.c')
  118. self._lib2 = os.path.join(self._dir2, 'libbar.so')
  119. # Update local site.cfg
  120. global simple_site, site_cfg
  121. site_cfg = simple_site.format(**{
  122. 'dir1': self._dir1,
  123. 'lib1': self._lib1,
  124. 'dir2': self._dir2,
  125. 'lib2': self._lib2,
  126. 'pathsep': os.pathsep,
  127. 'lib2_escaped': _shell_utils.NativeParser.join([self._lib2])
  128. })
  129. # Write site.cfg
  130. fd, self._sitecfg = mkstemp()
  131. os.close(fd)
  132. with open(self._sitecfg, 'w') as fd:
  133. fd.write(site_cfg)
  134. # Write the sources
  135. with open(self._src1, 'w') as fd:
  136. fd.write(fakelib_c_text)
  137. with open(self._src2, 'w') as fd:
  138. fd.write(fakelib_c_text)
  139. # We create all class-instances
  140. def site_and_parse(c, site_cfg):
  141. c.files = [site_cfg]
  142. c.parse_config_files()
  143. return c
  144. self.c_default = site_and_parse(get_class('default'), self._sitecfg)
  145. self.c_temp1 = site_and_parse(get_class('temp1'), self._sitecfg)
  146. self.c_temp2 = site_and_parse(get_class('temp2'), self._sitecfg)
  147. self.c_dup_options = site_and_parse(get_class('duplicate_options'),
  148. self._sitecfg)
  149. def teardown_method(self):
  150. # Do each removal separately
  151. try:
  152. shutil.rmtree(self._dir1)
  153. except Exception:
  154. pass
  155. try:
  156. shutil.rmtree(self._dir2)
  157. except Exception:
  158. pass
  159. try:
  160. os.remove(self._sitecfg)
  161. except Exception:
  162. pass
  163. def test_all(self):
  164. # Read in all information in the ALL block
  165. tsi = self.c_default
  166. assert_equal(tsi.get_lib_dirs(), [self._dir1, self._dir2])
  167. assert_equal(tsi.get_libraries(), [self._lib1, self._lib2])
  168. assert_equal(tsi.get_runtime_lib_dirs(), [self._dir1])
  169. extra = tsi.calc_extra_info()
  170. assert_equal(extra['extra_compile_args'], ['-I/fake/directory', '-I/path with/spaces', '-Os'])
  171. def test_temp1(self):
  172. # Read in all information in the temp1 block
  173. tsi = self.c_temp1
  174. assert_equal(tsi.get_lib_dirs(), [self._dir1])
  175. assert_equal(tsi.get_libraries(), [self._lib1])
  176. assert_equal(tsi.get_runtime_lib_dirs(), [self._dir1])
  177. def test_temp2(self):
  178. # Read in all information in the temp2 block
  179. tsi = self.c_temp2
  180. assert_equal(tsi.get_lib_dirs(), [self._dir2])
  181. assert_equal(tsi.get_libraries(), [self._lib2])
  182. # Now from rpath and not runtime_library_dirs
  183. assert_equal(tsi.get_runtime_lib_dirs(key='rpath'), [self._dir2])
  184. extra = tsi.calc_extra_info()
  185. assert_equal(extra['extra_link_args'], ['-Wl,-rpath=' + self._lib2])
  186. def test_duplicate_options(self):
  187. # Ensure that duplicates are raising an AliasedOptionError
  188. tsi = self.c_dup_options
  189. assert_raises(AliasedOptionError, tsi.get_option_single, "mylib_libs", "libraries")
  190. assert_equal(tsi.get_libs("mylib_libs", [self._lib1]), [self._lib1])
  191. assert_equal(tsi.get_libs("libraries", [self._lib2]), [self._lib2])
  192. @pytest.mark.skipif(not HAVE_COMPILER, reason="Missing compiler")
  193. def test_compile1(self):
  194. # Compile source and link the first source
  195. c = customized_ccompiler()
  196. previousDir = os.getcwd()
  197. try:
  198. # Change directory to not screw up directories
  199. os.chdir(self._dir1)
  200. c.compile([os.path.basename(self._src1)], output_dir=self._dir1)
  201. # Ensure that the object exists
  202. assert_(os.path.isfile(self._src1.replace('.c', '.o')) or
  203. os.path.isfile(self._src1.replace('.c', '.obj')))
  204. finally:
  205. os.chdir(previousDir)
  206. @pytest.mark.skipif(not HAVE_COMPILER, reason="Missing compiler")
  207. @pytest.mark.skipif('msvc' in repr(ccompiler.new_compiler()),
  208. reason="Fails with MSVC compiler ")
  209. def test_compile2(self):
  210. # Compile source and link the second source
  211. tsi = self.c_temp2
  212. c = customized_ccompiler()
  213. extra_link_args = tsi.calc_extra_info()['extra_link_args']
  214. previousDir = os.getcwd()
  215. try:
  216. # Change directory to not screw up directories
  217. os.chdir(self._dir2)
  218. c.compile([os.path.basename(self._src2)], output_dir=self._dir2,
  219. extra_postargs=extra_link_args)
  220. # Ensure that the object exists
  221. assert_(os.path.isfile(self._src2.replace('.c', '.o')))
  222. finally:
  223. os.chdir(previousDir)
  224. HAS_MKL = "mkl_rt" in mkl_info().calc_libraries_info().get("libraries", [])
  225. @pytest.mark.xfail(HAS_MKL, reason=("`[DEFAULT]` override doesn't work if "
  226. "numpy is built with MKL support"))
  227. def test_overrides(self):
  228. previousDir = os.getcwd()
  229. cfg = os.path.join(self._dir1, 'site.cfg')
  230. shutil.copy(self._sitecfg, cfg)
  231. try:
  232. os.chdir(self._dir1)
  233. # Check that the '[ALL]' section does not override
  234. # missing values from other sections
  235. info = mkl_info()
  236. lib_dirs = info.cp['ALL']['library_dirs'].split(os.pathsep)
  237. assert info.get_lib_dirs() != lib_dirs
  238. # But if we copy the values to a '[mkl]' section the value
  239. # is correct
  240. with open(cfg, 'r') as fid:
  241. mkl = fid.read().replace('[ALL]', '[mkl]', 1)
  242. with open(cfg, 'w') as fid:
  243. fid.write(mkl)
  244. info = mkl_info()
  245. assert info.get_lib_dirs() == lib_dirs
  246. # Also, the values will be taken from a section named '[DEFAULT]'
  247. with open(cfg, 'r') as fid:
  248. dflt = fid.read().replace('[mkl]', '[DEFAULT]', 1)
  249. with open(cfg, 'w') as fid:
  250. fid.write(dflt)
  251. info = mkl_info()
  252. assert info.get_lib_dirs() == lib_dirs
  253. finally:
  254. os.chdir(previousDir)
  255. def test_distutils_parse_env_order(monkeypatch):
  256. from numpy.distutils.system_info import _parse_env_order
  257. env = 'NPY_TESTS_DISTUTILS_PARSE_ENV_ORDER'
  258. base_order = list('abcdef')
  259. monkeypatch.setenv(env, 'b,i,e,f')
  260. order, unknown = _parse_env_order(base_order, env)
  261. assert len(order) == 3
  262. assert order == list('bef')
  263. assert len(unknown) == 1
  264. # For when LAPACK/BLAS optimization is disabled
  265. monkeypatch.setenv(env, '')
  266. order, unknown = _parse_env_order(base_order, env)
  267. assert len(order) == 0
  268. assert len(unknown) == 0
  269. for prefix in '^!':
  270. monkeypatch.setenv(env, f'{prefix}b,i,e')
  271. order, unknown = _parse_env_order(base_order, env)
  272. assert len(order) == 4
  273. assert order == list('acdf')
  274. assert len(unknown) == 1
  275. with pytest.raises(ValueError):
  276. monkeypatch.setenv(env, 'b,^e,i')
  277. _parse_env_order(base_order, env)
  278. with pytest.raises(ValueError):
  279. monkeypatch.setenv(env, '!b,^e,i')
  280. _parse_env_order(base_order, env)