test_sysconfig.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. """Tests for distutils.sysconfig."""
  2. import contextlib
  3. import distutils
  4. import os
  5. import pathlib
  6. import subprocess
  7. import sys
  8. from distutils import sysconfig
  9. from distutils.ccompiler import new_compiler # noqa: F401
  10. from distutils.unixccompiler import UnixCCompiler
  11. import jaraco.envs
  12. import path
  13. import pytest
  14. from jaraco.text import trim
  15. from test.support import swap_item
  16. def _gen_makefile(root, contents):
  17. jaraco.path.build({'Makefile': trim(contents)}, root)
  18. return root / 'Makefile'
  19. @pytest.mark.usefixtures('save_env')
  20. class TestSysconfig:
  21. def test_get_config_h_filename(self):
  22. config_h = sysconfig.get_config_h_filename()
  23. assert os.path.isfile(config_h)
  24. @pytest.mark.skipif("platform.system() == 'Windows'")
  25. @pytest.mark.skipif("sys.implementation.name != 'cpython'")
  26. def test_get_makefile_filename(self):
  27. makefile = sysconfig.get_makefile_filename()
  28. assert os.path.isfile(makefile)
  29. def test_get_python_lib(self, tmp_path):
  30. assert sysconfig.get_python_lib() != sysconfig.get_python_lib(prefix=tmp_path)
  31. def test_get_config_vars(self):
  32. cvars = sysconfig.get_config_vars()
  33. assert isinstance(cvars, dict)
  34. assert cvars
  35. @pytest.mark.skipif('sysconfig.IS_PYPY')
  36. @pytest.mark.skipif('sysconfig.python_build')
  37. @pytest.mark.xfail('platform.system() == "Windows"')
  38. def test_srcdir_simple(self):
  39. # See #15364.
  40. srcdir = pathlib.Path(sysconfig.get_config_var('srcdir'))
  41. assert srcdir.absolute()
  42. assert srcdir.is_dir()
  43. makefile = pathlib.Path(sysconfig.get_makefile_filename())
  44. assert makefile.parent.samefile(srcdir)
  45. @pytest.mark.skipif('sysconfig.IS_PYPY')
  46. @pytest.mark.skipif('not sysconfig.python_build')
  47. def test_srcdir_python_build(self):
  48. # See #15364.
  49. srcdir = pathlib.Path(sysconfig.get_config_var('srcdir'))
  50. # The python executable has not been installed so srcdir
  51. # should be a full source checkout.
  52. Python_h = srcdir.joinpath('Include', 'Python.h')
  53. assert Python_h.is_file()
  54. assert sysconfig._is_python_source_dir(srcdir)
  55. assert sysconfig._is_python_source_dir(str(srcdir))
  56. def test_srcdir_independent_of_cwd(self):
  57. """
  58. srcdir should be independent of the current working directory
  59. """
  60. # See #15364.
  61. srcdir = sysconfig.get_config_var('srcdir')
  62. with path.Path('..'):
  63. srcdir2 = sysconfig.get_config_var('srcdir')
  64. assert srcdir == srcdir2
  65. def customize_compiler(self):
  66. # make sure AR gets caught
  67. class compiler:
  68. compiler_type = 'unix'
  69. executables = UnixCCompiler.executables
  70. def __init__(self):
  71. self.exes = {}
  72. def set_executables(self, **kw):
  73. for k, v in kw.items():
  74. self.exes[k] = v
  75. sysconfig_vars = {
  76. 'AR': 'sc_ar',
  77. 'CC': 'sc_cc',
  78. 'CXX': 'sc_cxx',
  79. 'ARFLAGS': '--sc-arflags',
  80. 'CFLAGS': '--sc-cflags',
  81. 'CCSHARED': '--sc-ccshared',
  82. 'LDSHARED': 'sc_ldshared',
  83. 'SHLIB_SUFFIX': 'sc_shutil_suffix',
  84. }
  85. comp = compiler()
  86. with contextlib.ExitStack() as cm:
  87. for key, value in sysconfig_vars.items():
  88. cm.enter_context(swap_item(sysconfig._config_vars, key, value))
  89. sysconfig.customize_compiler(comp)
  90. return comp
  91. @pytest.mark.skipif("not isinstance(new_compiler(), UnixCCompiler)")
  92. @pytest.mark.usefixtures('disable_macos_customization')
  93. def test_customize_compiler(self):
  94. # Make sure that sysconfig._config_vars is initialized
  95. sysconfig.get_config_vars()
  96. os.environ['AR'] = 'env_ar'
  97. os.environ['CC'] = 'env_cc'
  98. os.environ['CPP'] = 'env_cpp'
  99. os.environ['CXX'] = 'env_cxx --env-cxx-flags'
  100. os.environ['LDSHARED'] = 'env_ldshared'
  101. os.environ['LDFLAGS'] = '--env-ldflags'
  102. os.environ['ARFLAGS'] = '--env-arflags'
  103. os.environ['CFLAGS'] = '--env-cflags'
  104. os.environ['CPPFLAGS'] = '--env-cppflags'
  105. os.environ['RANLIB'] = 'env_ranlib'
  106. comp = self.customize_compiler()
  107. assert comp.exes['archiver'] == 'env_ar --env-arflags'
  108. assert comp.exes['preprocessor'] == 'env_cpp --env-cppflags'
  109. assert comp.exes['compiler'] == 'env_cc --env-cflags --env-cppflags'
  110. assert comp.exes['compiler_so'] == (
  111. 'env_cc --env-cflags --env-cppflags --sc-ccshared'
  112. )
  113. assert (
  114. comp.exes['compiler_cxx']
  115. == 'env_cxx --env-cxx-flags --sc-cflags --env-cppflags'
  116. )
  117. assert comp.exes['linker_exe'] == 'env_cc'
  118. assert comp.exes['linker_so'] == (
  119. 'env_ldshared --env-ldflags --env-cflags --env-cppflags'
  120. )
  121. assert comp.shared_lib_extension == 'sc_shutil_suffix'
  122. if sys.platform == "darwin":
  123. assert comp.exes['ranlib'] == 'env_ranlib'
  124. else:
  125. assert 'ranlib' not in comp.exes
  126. del os.environ['AR']
  127. del os.environ['CC']
  128. del os.environ['CPP']
  129. del os.environ['CXX']
  130. del os.environ['LDSHARED']
  131. del os.environ['LDFLAGS']
  132. del os.environ['ARFLAGS']
  133. del os.environ['CFLAGS']
  134. del os.environ['CPPFLAGS']
  135. del os.environ['RANLIB']
  136. comp = self.customize_compiler()
  137. assert comp.exes['archiver'] == 'sc_ar --sc-arflags'
  138. assert comp.exes['preprocessor'] == 'sc_cc -E'
  139. assert comp.exes['compiler'] == 'sc_cc --sc-cflags'
  140. assert comp.exes['compiler_so'] == 'sc_cc --sc-cflags --sc-ccshared'
  141. assert comp.exes['compiler_cxx'] == 'sc_cxx --sc-cflags'
  142. assert comp.exes['linker_exe'] == 'sc_cc'
  143. assert comp.exes['linker_so'] == 'sc_ldshared'
  144. assert comp.shared_lib_extension == 'sc_shutil_suffix'
  145. assert 'ranlib' not in comp.exes
  146. def test_parse_makefile_base(self, tmp_path):
  147. makefile = _gen_makefile(
  148. tmp_path,
  149. """
  150. CONFIG_ARGS= '--arg1=optarg1' 'ENV=LIB'
  151. VAR=$OTHER
  152. OTHER=foo
  153. """,
  154. )
  155. d = sysconfig.parse_makefile(makefile)
  156. assert d == {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'", 'OTHER': 'foo'}
  157. def test_parse_makefile_literal_dollar(self, tmp_path):
  158. makefile = _gen_makefile(
  159. tmp_path,
  160. """
  161. CONFIG_ARGS= '--arg1=optarg1' 'ENV=\\$$LIB'
  162. VAR=$OTHER
  163. OTHER=foo
  164. """,
  165. )
  166. d = sysconfig.parse_makefile(makefile)
  167. assert d == {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'", 'OTHER': 'foo'}
  168. def test_sysconfig_module(self):
  169. import sysconfig as global_sysconfig
  170. assert global_sysconfig.get_config_var('CFLAGS') == sysconfig.get_config_var(
  171. 'CFLAGS'
  172. )
  173. assert global_sysconfig.get_config_var('LDFLAGS') == sysconfig.get_config_var(
  174. 'LDFLAGS'
  175. )
  176. # On macOS, binary installers support extension module building on
  177. # various levels of the operating system with differing Xcode
  178. # configurations, requiring customization of some of the
  179. # compiler configuration directives to suit the environment on
  180. # the installed machine. Some of these customizations may require
  181. # running external programs and are thus deferred until needed by
  182. # the first extension module build. Only
  183. # the Distutils version of sysconfig is used for extension module
  184. # builds, which happens earlier in the Distutils tests. This may
  185. # cause the following tests to fail since no tests have caused
  186. # the global version of sysconfig to call the customization yet.
  187. # The solution for now is to simply skip this test in this case.
  188. # The longer-term solution is to only have one version of sysconfig.
  189. @pytest.mark.skipif("sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER')")
  190. def test_sysconfig_compiler_vars(self):
  191. import sysconfig as global_sysconfig
  192. if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'):
  193. pytest.skip('compiler flags customized')
  194. assert global_sysconfig.get_config_var('LDSHARED') == sysconfig.get_config_var(
  195. 'LDSHARED'
  196. )
  197. assert global_sysconfig.get_config_var('CC') == sysconfig.get_config_var('CC')
  198. @pytest.mark.skipif("not sysconfig.get_config_var('EXT_SUFFIX')")
  199. def test_SO_deprecation(self):
  200. with pytest.warns(DeprecationWarning):
  201. sysconfig.get_config_var('SO')
  202. def test_customize_compiler_before_get_config_vars(self, tmp_path):
  203. # Issue #21923: test that a Distribution compiler
  204. # instance can be called without an explicit call to
  205. # get_config_vars().
  206. jaraco.path.build(
  207. {
  208. 'file': trim("""
  209. from distutils.core import Distribution
  210. config = Distribution().get_command_obj('config')
  211. # try_compile may pass or it may fail if no compiler
  212. # is found but it should not raise an exception.
  213. rc = config.try_compile('int x;')
  214. """)
  215. },
  216. tmp_path,
  217. )
  218. p = subprocess.Popen(
  219. [sys.executable, tmp_path / 'file'],
  220. stdout=subprocess.PIPE,
  221. stderr=subprocess.STDOUT,
  222. universal_newlines=True,
  223. encoding='utf-8',
  224. )
  225. outs, errs = p.communicate()
  226. assert 0 == p.returncode, "Subprocess failed: " + outs
  227. def test_parse_config_h(self):
  228. config_h = sysconfig.get_config_h_filename()
  229. input = {}
  230. with open(config_h, encoding="utf-8") as f:
  231. result = sysconfig.parse_config_h(f, g=input)
  232. assert input is result
  233. with open(config_h, encoding="utf-8") as f:
  234. result = sysconfig.parse_config_h(f)
  235. assert isinstance(result, dict)
  236. @pytest.mark.skipif("platform.system() != 'Windows'")
  237. @pytest.mark.skipif("sys.implementation.name != 'cpython'")
  238. def test_win_ext_suffix(self):
  239. assert sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd")
  240. assert sysconfig.get_config_var("EXT_SUFFIX") != ".pyd"
  241. @pytest.mark.skipif("platform.system() != 'Windows'")
  242. @pytest.mark.skipif("sys.implementation.name != 'cpython'")
  243. @pytest.mark.skipif(
  244. '\\PCbuild\\'.casefold() not in sys.executable.casefold(),
  245. reason='Need sys.executable to be in a source tree',
  246. )
  247. def test_win_build_venv_from_source_tree(self, tmp_path):
  248. """Ensure distutils.sysconfig detects venvs from source tree builds."""
  249. env = jaraco.envs.VEnv()
  250. env.create_opts = env.clean_opts
  251. env.root = tmp_path
  252. env.ensure_env()
  253. cmd = [
  254. env.exe(),
  255. "-c",
  256. "import distutils.sysconfig; print(distutils.sysconfig.python_build)",
  257. ]
  258. distutils_path = os.path.dirname(os.path.dirname(distutils.__file__))
  259. out = subprocess.check_output(
  260. cmd, env={**os.environ, "PYTHONPATH": distutils_path}
  261. )
  262. assert out == "True"
  263. def test_get_python_inc_missing_config_dir(self, monkeypatch):
  264. """
  265. In portable Python installations, the sysconfig will be broken,
  266. pointing to the directories where the installation was built and
  267. not where it currently is. In this case, ensure that the missing
  268. directory isn't used for get_python_inc.
  269. See pypa/distutils#178.
  270. """
  271. def override(name):
  272. if name == 'INCLUDEPY':
  273. return '/does-not-exist'
  274. return sysconfig.get_config_var(name)
  275. monkeypatch.setattr(sysconfig, 'get_config_var', override)
  276. assert os.path.exists(sysconfig.get_python_inc())