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