1"""Tests for distutils.sysconfig.""" 2import contextlib 3import os 4import shutil 5import subprocess 6import sys 7import textwrap 8import unittest 9 10from distutils import sysconfig 11from distutils.ccompiler import get_default_compiler 12from distutils.tests import support 13from test.support import run_unittest, swap_item, requires_subprocess, is_wasi 14from test.support.os_helper import TESTFN 15from test.support.warnings_helper import check_warnings 16 17 18class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): 19 def setUp(self): 20 super(SysconfigTestCase, self).setUp() 21 self.makefile = None 22 23 def tearDown(self): 24 if self.makefile is not None: 25 os.unlink(self.makefile) 26 self.cleanup_testfn() 27 super(SysconfigTestCase, self).tearDown() 28 29 def cleanup_testfn(self): 30 if os.path.isfile(TESTFN): 31 os.remove(TESTFN) 32 elif os.path.isdir(TESTFN): 33 shutil.rmtree(TESTFN) 34 35 @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") 36 def test_get_config_h_filename(self): 37 config_h = sysconfig.get_config_h_filename() 38 self.assertTrue(os.path.isfile(config_h), config_h) 39 40 def test_get_python_lib(self): 41 # XXX doesn't work on Linux when Python was never installed before 42 #self.assertTrue(os.path.isdir(lib_dir), lib_dir) 43 # test for pythonxx.lib? 44 self.assertNotEqual(sysconfig.get_python_lib(), 45 sysconfig.get_python_lib(prefix=TESTFN)) 46 47 def test_get_config_vars(self): 48 cvars = sysconfig.get_config_vars() 49 self.assertIsInstance(cvars, dict) 50 self.assertTrue(cvars) 51 52 @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") 53 def test_srcdir(self): 54 # See Issues #15322, #15364. 55 srcdir = sysconfig.get_config_var('srcdir') 56 57 self.assertTrue(os.path.isabs(srcdir), srcdir) 58 self.assertTrue(os.path.isdir(srcdir), srcdir) 59 60 if sysconfig.python_build: 61 # The python executable has not been installed so srcdir 62 # should be a full source checkout. 63 Python_h = os.path.join(srcdir, 'Include', 'Python.h') 64 self.assertTrue(os.path.exists(Python_h), Python_h) 65 # <srcdir>/PC/pyconfig.h always exists even if unused on POSIX. 66 pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h') 67 self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) 68 pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in') 69 self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in) 70 elif os.name == 'posix': 71 self.assertEqual( 72 os.path.dirname(sysconfig.get_makefile_filename()), 73 srcdir) 74 75 def test_srcdir_independent_of_cwd(self): 76 # srcdir should be independent of the current working directory 77 # See Issues #15322, #15364. 78 srcdir = sysconfig.get_config_var('srcdir') 79 cwd = os.getcwd() 80 try: 81 os.chdir('..') 82 srcdir2 = sysconfig.get_config_var('srcdir') 83 finally: 84 os.chdir(cwd) 85 self.assertEqual(srcdir, srcdir2) 86 87 def customize_compiler(self): 88 # make sure AR gets caught 89 class compiler: 90 compiler_type = 'unix' 91 92 def set_executables(self, **kw): 93 self.exes = kw 94 95 sysconfig_vars = { 96 'AR': 'sc_ar', 97 'CC': 'sc_cc', 98 'CXX': 'sc_cxx', 99 'ARFLAGS': '--sc-arflags', 100 'CFLAGS': '--sc-cflags', 101 'CCSHARED': '--sc-ccshared', 102 'LDSHARED': 'sc_ldshared', 103 'SHLIB_SUFFIX': 'sc_shutil_suffix', 104 105 # On macOS, disable _osx_support.customize_compiler() 106 'CUSTOMIZED_OSX_COMPILER': 'True', 107 } 108 109 comp = compiler() 110 with contextlib.ExitStack() as cm: 111 for key, value in sysconfig_vars.items(): 112 cm.enter_context(swap_item(sysconfig._config_vars, key, value)) 113 sysconfig.customize_compiler(comp) 114 115 return comp 116 117 @unittest.skipUnless(get_default_compiler() == 'unix', 118 'not testing if default compiler is not unix') 119 def test_customize_compiler(self): 120 # Make sure that sysconfig._config_vars is initialized 121 sysconfig.get_config_vars() 122 123 os.environ['AR'] = 'env_ar' 124 os.environ['CC'] = 'env_cc' 125 os.environ['CPP'] = 'env_cpp' 126 os.environ['CXX'] = 'env_cxx --env-cxx-flags' 127 os.environ['LDSHARED'] = 'env_ldshared' 128 os.environ['LDFLAGS'] = '--env-ldflags' 129 os.environ['ARFLAGS'] = '--env-arflags' 130 os.environ['CFLAGS'] = '--env-cflags' 131 os.environ['CPPFLAGS'] = '--env-cppflags' 132 133 comp = self.customize_compiler() 134 self.assertEqual(comp.exes['archiver'], 135 'env_ar --env-arflags') 136 self.assertEqual(comp.exes['preprocessor'], 137 'env_cpp --env-cppflags') 138 self.assertEqual(comp.exes['compiler'], 139 'env_cc --sc-cflags --env-cflags --env-cppflags') 140 self.assertEqual(comp.exes['compiler_so'], 141 ('env_cc --sc-cflags ' 142 '--env-cflags ''--env-cppflags --sc-ccshared')) 143 self.assertEqual(comp.exes['compiler_cxx'], 144 'env_cxx --env-cxx-flags') 145 self.assertEqual(comp.exes['linker_exe'], 146 'env_cc') 147 self.assertEqual(comp.exes['linker_so'], 148 ('env_ldshared --env-ldflags --env-cflags' 149 ' --env-cppflags')) 150 self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') 151 152 del os.environ['AR'] 153 del os.environ['CC'] 154 del os.environ['CPP'] 155 del os.environ['CXX'] 156 del os.environ['LDSHARED'] 157 del os.environ['LDFLAGS'] 158 del os.environ['ARFLAGS'] 159 del os.environ['CFLAGS'] 160 del os.environ['CPPFLAGS'] 161 162 comp = self.customize_compiler() 163 self.assertEqual(comp.exes['archiver'], 164 'sc_ar --sc-arflags') 165 self.assertEqual(comp.exes['preprocessor'], 166 'sc_cc -E') 167 self.assertEqual(comp.exes['compiler'], 168 'sc_cc --sc-cflags') 169 self.assertEqual(comp.exes['compiler_so'], 170 'sc_cc --sc-cflags --sc-ccshared') 171 self.assertEqual(comp.exes['compiler_cxx'], 172 'sc_cxx') 173 self.assertEqual(comp.exes['linker_exe'], 174 'sc_cc') 175 self.assertEqual(comp.exes['linker_so'], 176 'sc_ldshared') 177 self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') 178 179 def test_parse_makefile_base(self): 180 self.makefile = TESTFN 181 fd = open(self.makefile, 'w') 182 try: 183 fd.write(r"CONFIG_ARGS= '--arg1=optarg1' 'ENV=LIB'" '\n') 184 fd.write('VAR=$OTHER\nOTHER=foo') 185 finally: 186 fd.close() 187 d = sysconfig.parse_makefile(self.makefile) 188 self.assertEqual(d, {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'", 189 'OTHER': 'foo'}) 190 191 def test_parse_makefile_literal_dollar(self): 192 self.makefile = TESTFN 193 fd = open(self.makefile, 'w') 194 try: 195 fd.write(r"CONFIG_ARGS= '--arg1=optarg1' 'ENV=\$$LIB'" '\n') 196 fd.write('VAR=$OTHER\nOTHER=foo') 197 finally: 198 fd.close() 199 d = sysconfig.parse_makefile(self.makefile) 200 self.assertEqual(d, {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'", 201 'OTHER': 'foo'}) 202 203 204 def test_sysconfig_module(self): 205 import sysconfig as global_sysconfig 206 self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), 207 sysconfig.get_config_var('CFLAGS')) 208 self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), 209 sysconfig.get_config_var('LDFLAGS')) 210 211 @unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'), 212 'compiler flags customized') 213 def test_sysconfig_compiler_vars(self): 214 # On OS X, binary installers support extension module building on 215 # various levels of the operating system with differing Xcode 216 # configurations. This requires customization of some of the 217 # compiler configuration directives to suit the environment on 218 # the installed machine. Some of these customizations may require 219 # running external programs and, so, are deferred until needed by 220 # the first extension module build. With Python 3.3, only 221 # the Distutils version of sysconfig is used for extension module 222 # builds, which happens earlier in the Distutils tests. This may 223 # cause the following tests to fail since no tests have caused 224 # the global version of sysconfig to call the customization yet. 225 # The solution for now is to simply skip this test in this case. 226 # The longer-term solution is to only have one version of sysconfig. 227 228 import sysconfig as global_sysconfig 229 if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'): 230 self.skipTest('compiler flags customized') 231 self.assertEqual(global_sysconfig.get_config_var('LDSHARED'), 232 sysconfig.get_config_var('LDSHARED')) 233 self.assertEqual(global_sysconfig.get_config_var('CC'), 234 sysconfig.get_config_var('CC')) 235 236 @requires_subprocess() 237 def test_customize_compiler_before_get_config_vars(self): 238 # Issue #21923: test that a Distribution compiler 239 # instance can be called without an explicit call to 240 # get_config_vars(). 241 with open(TESTFN, 'w') as f: 242 f.writelines(textwrap.dedent('''\ 243 from distutils.core import Distribution 244 config = Distribution().get_command_obj('config') 245 # try_compile may pass or it may fail if no compiler 246 # is found but it should not raise an exception. 247 rc = config.try_compile('int x;') 248 ''')) 249 p = subprocess.Popen([str(sys.executable), TESTFN], 250 stdout=subprocess.PIPE, 251 stderr=subprocess.STDOUT, 252 universal_newlines=True) 253 outs, errs = p.communicate() 254 self.assertEqual(0, p.returncode, "Subprocess failed: " + outs) 255 256 257def test_suite(): 258 suite = unittest.TestSuite() 259 suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SysconfigTestCase)) 260 return suite 261 262 263if __name__ == '__main__': 264 run_unittest(test_suite()) 265