codekingpro/portable-devtools
114k
1"""Tests for distutils.unixccompiler."""2 3import os4import sys5import unittest.mock as mock6from distutils import sysconfig7from distutils.compat import consolidate_linker_args8from distutils.errors import DistutilsPlatformError9from distutils.tests import support10from distutils.tests.compat.py39 import EnvironmentVarGuard11from distutils.util import _clear_cached_macosx_ver12 13import pytest14 15from .. import unix16 17 18@pytest.fixture(autouse=True)19def save_values(monkeypatch):20 monkeypatch.setattr(sys, 'platform', sys.platform)21 monkeypatch.setattr(sysconfig, 'get_config_var', sysconfig.get_config_var)22 monkeypatch.setattr(sysconfig, 'get_config_vars', sysconfig.get_config_vars)23 24 25@pytest.fixture(autouse=True)26def compiler_wrapper(request):27 class CompilerWrapper(unix.Compiler):28 def rpath_foo(self):29 return self.runtime_library_dir_option('/foo')30 31 request.instance.cc = CompilerWrapper()32 33 34class TestUnixCCompiler(support.TempdirManager):35 @pytest.mark.skipif('platform.system == "Windows"')36 def test_runtime_libdir_option(self): # noqa: C90137 # Issue #5900; GitHub Issue #3738 #39 # Ensure RUNPATH is added to extension modules with RPATH if40 # GNU ld is used41 42 # darwin43 sys.platform = 'darwin'44 darwin_ver_var = 'MACOSX_DEPLOYMENT_TARGET'45 darwin_rpath_flag = '-Wl,-rpath,/foo'46 darwin_lib_flag = '-L/foo'47 48 # (macOS version from syscfg, macOS version from env var) -> flag49 # Version value of None generates two tests: as None and as empty string50 # Expected flag value of None means an mismatch exception is expected51 darwin_test_cases = [52 ((None, None), darwin_lib_flag),53 ((None, '11'), darwin_rpath_flag),54 (('10', None), darwin_lib_flag),55 (('10.3', None), darwin_lib_flag),56 (('10.3.1', None), darwin_lib_flag),57 (('10.5', None), darwin_rpath_flag),58 (('10.5.1', None), darwin_rpath_flag),59 (('10.3', '10.3'), darwin_lib_flag),60 (('10.3', '10.5'), darwin_rpath_flag),61 (('10.5', '10.3'), darwin_lib_flag),62 (('10.5', '11'), darwin_rpath_flag),63 (('10.4', '10'), None),64 ]65 66 def make_darwin_gcv(syscfg_macosx_ver):67 def gcv(var):68 if var == darwin_ver_var:69 return syscfg_macosx_ver70 return "xxx"71 72 return gcv73 74 def do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag):75 env = os.environ76 msg = f"macOS version = (sysconfig={syscfg_macosx_ver!r}, env={env_macosx_ver!r})"77 78 # Save79 old_gcv = sysconfig.get_config_var80 old_env_macosx_ver = env.get(darwin_ver_var)81 82 # Setup environment83 _clear_cached_macosx_ver()84 sysconfig.get_config_var = make_darwin_gcv(syscfg_macosx_ver)85 if env_macosx_ver is not None:86 env[darwin_ver_var] = env_macosx_ver87 elif darwin_ver_var in env:88 env.pop(darwin_ver_var)89 90 # Run the test91 if expected_flag is not None:92 assert self.cc.rpath_foo() == expected_flag, msg93 else:94 with pytest.raises(95 DistutilsPlatformError, match=darwin_ver_var + r' mismatch'96 ):97 self.cc.rpath_foo()98 99 # Restore100 if old_env_macosx_ver is not None:101 env[darwin_ver_var] = old_env_macosx_ver102 elif darwin_ver_var in env:103 env.pop(darwin_ver_var)104 sysconfig.get_config_var = old_gcv105 _clear_cached_macosx_ver()106 107 for macosx_vers, expected_flag in darwin_test_cases:108 syscfg_macosx_ver, env_macosx_ver = macosx_vers109 do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag)110 # Bonus test cases with None interpreted as empty string111 if syscfg_macosx_ver is None:112 do_darwin_test("", env_macosx_ver, expected_flag)113 if env_macosx_ver is None:114 do_darwin_test(syscfg_macosx_ver, "", expected_flag)115 if syscfg_macosx_ver is None and env_macosx_ver is None:116 do_darwin_test("", "", expected_flag)117 118 old_gcv = sysconfig.get_config_var119 120 # hp-ux121 sys.platform = 'hp-ux'122 123 def gcv(v):124 return 'xxx'125 126 sysconfig.get_config_var = gcv127 assert self.cc.rpath_foo() == ['+s', '-L/foo']128 129 def gcv(v):130 return 'gcc'131 132 sysconfig.get_config_var = gcv133 assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo']134 135 def gcv(v):136 return 'g++'137 138 sysconfig.get_config_var = gcv139 assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo']140 141 sysconfig.get_config_var = old_gcv142 143 # GCC GNULD144 sys.platform = 'bar'145 146 def gcv(v):147 if v == 'CC':148 return 'gcc'149 elif v == 'GNULD':150 return 'yes'151 152 sysconfig.get_config_var = gcv153 assert self.cc.rpath_foo() == consolidate_linker_args([154 '-Wl,--enable-new-dtags',155 '-Wl,-rpath,/foo',156 ])157 158 def gcv(v):159 if v == 'CC':160 return 'gcc -pthread -B /bar'161 elif v == 'GNULD':162 return 'yes'163 164 sysconfig.get_config_var = gcv165 assert self.cc.rpath_foo() == consolidate_linker_args([166 '-Wl,--enable-new-dtags',167 '-Wl,-rpath,/foo',168 ])169 170 # GCC non-GNULD171 sys.platform = 'bar'172 173 def gcv(v):174 if v == 'CC':175 return 'gcc'176 elif v == 'GNULD':177 return 'no'178 179 sysconfig.get_config_var = gcv180 assert self.cc.rpath_foo() == '-Wl,-R/foo'181 182 # GCC GNULD with fully qualified configuration prefix183 # see #7617184 sys.platform = 'bar'185 186 def gcv(v):187 if v == 'CC':188 return 'x86_64-pc-linux-gnu-gcc-4.4.2'189 elif v == 'GNULD':190 return 'yes'191 192 sysconfig.get_config_var = gcv193 assert self.cc.rpath_foo() == consolidate_linker_args([194 '-Wl,--enable-new-dtags',195 '-Wl,-rpath,/foo',196 ])197 198 # non-GCC GNULD199 sys.platform = 'bar'200 201 def gcv(v):202 if v == 'CC':203 return 'cc'204 elif v == 'GNULD':205 return 'yes'206 207 sysconfig.get_config_var = gcv208 assert self.cc.rpath_foo() == consolidate_linker_args([209 '-Wl,--enable-new-dtags',210 '-Wl,-rpath,/foo',211 ])212 213 # non-GCC non-GNULD214 sys.platform = 'bar'215 216 def gcv(v):217 if v == 'CC':218 return 'cc'219 elif v == 'GNULD':220 return 'no'221 222 sysconfig.get_config_var = gcv223 assert self.cc.rpath_foo() == '-Wl,-R/foo'224 225 @pytest.mark.skipif('platform.system == "Windows"')226 def test_cc_overrides_ldshared(self):227 # Issue #18080:228 # ensure that setting CC env variable also changes default linker229 def gcv(v):230 if v == 'LDSHARED':231 return 'gcc-4.2 -bundle -undefined dynamic_lookup '232 return 'gcc-4.2'233 234 def gcvs(*args, _orig=sysconfig.get_config_vars):235 if args:236 return list(map(sysconfig.get_config_var, args))237 return _orig()238 239 sysconfig.get_config_var = gcv240 sysconfig.get_config_vars = gcvs241 with EnvironmentVarGuard() as env:242 env['CC'] = 'my_cc'243 del env['LDSHARED']244 sysconfig.customize_compiler(self.cc)245 assert self.cc.linker_so[0] == 'my_cc'246 247 @pytest.mark.skipif('platform.system == "Windows"')248 def test_cxx_commands_used_are_correct(self):249 def gcv(v):250 if v == 'LDSHARED':251 return 'ccache gcc-4.2 -bundle -undefined dynamic_lookup'252 elif v == 'LDCXXSHARED':253 return 'ccache g++-4.2 -bundle -undefined dynamic_lookup'254 elif v == 'CXX':255 return 'ccache g++-4.2'256 elif v == 'CC':257 return 'ccache gcc-4.2'258 return ''259 260 def gcvs(*args, _orig=sysconfig.get_config_vars):261 if args:262 return list(map(sysconfig.get_config_var, args))263 return _orig() # pragma: no cover264 265 sysconfig.get_config_var = gcv266 sysconfig.get_config_vars = gcvs267 with (268 mock.patch.object(self.cc, 'spawn', return_value=None) as mock_spawn,269 mock.patch.object(self.cc, '_need_link', return_value=True),270 mock.patch.object(self.cc, 'mkpath', return_value=None),271 EnvironmentVarGuard() as env,272 ):273 # override environment overrides in case they're specified by CI274 del env['CXX']275 del env['LDCXXSHARED']276 277 sysconfig.customize_compiler(self.cc)278 assert self.cc.linker_so_cxx[0:2] == ['ccache', 'g++-4.2']279 assert self.cc.linker_exe_cxx[0:2] == ['ccache', 'g++-4.2']280 self.cc.link(None, [], 'a.out', target_lang='c++')281 call_args = mock_spawn.call_args[0][0]282 expected = ['ccache', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']283 assert call_args[:5] == expected284 285 self.cc.link_executable([], 'a.out', target_lang='c++')286 call_args = mock_spawn.call_args[0][0]287 expected = ['ccache', 'g++-4.2', '-o', self.cc.executable_filename('a.out')]288 assert call_args[:4] == expected289 290 env['LDCXXSHARED'] = 'wrapper g++-4.2 -bundle -undefined dynamic_lookup'291 env['CXX'] = 'wrapper g++-4.2'292 sysconfig.customize_compiler(self.cc)293 assert self.cc.linker_so_cxx[0:2] == ['wrapper', 'g++-4.2']294 assert self.cc.linker_exe_cxx[0:2] == ['wrapper', 'g++-4.2']295 self.cc.link(None, [], 'a.out', target_lang='c++')296 call_args = mock_spawn.call_args[0][0]297 expected = ['wrapper', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']298 assert call_args[:5] == expected299 300 self.cc.link_executable([], 'a.out', target_lang='c++')301 call_args = mock_spawn.call_args[0][0]302 expected = [303 'wrapper',304 'g++-4.2',305 '-o',306 self.cc.executable_filename('a.out'),307 ]308 assert call_args[:4] == expected309 310 @pytest.mark.skipif('platform.system == "Windows"')311 @pytest.mark.usefixtures('disable_macos_customization')312 def test_cc_overrides_ldshared_for_cxx_correctly(self):313 """314 Ensure that setting CC env variable also changes default linker315 correctly when building C++ extensions.316 317 pypa/distutils#126318 """319 320 def gcv(v):321 if v == 'LDSHARED':322 return 'gcc-4.2 -bundle -undefined dynamic_lookup '323 elif v == 'LDCXXSHARED':324 return 'g++-4.2 -bundle -undefined dynamic_lookup '325 elif v == 'CXX':326 return 'g++-4.2'327 elif v == 'CC':328 return 'gcc-4.2'329 return ''330 331 def gcvs(*args, _orig=sysconfig.get_config_vars):332 if args:333 return list(map(sysconfig.get_config_var, args))334 return _orig()335 336 sysconfig.get_config_var = gcv337 sysconfig.get_config_vars = gcvs338 with (339 mock.patch.object(self.cc, 'spawn', return_value=None) as mock_spawn,340 mock.patch.object(self.cc, '_need_link', return_value=True),341 mock.patch.object(self.cc, 'mkpath', return_value=None),342 EnvironmentVarGuard() as env,343 ):344 env['CC'] = 'ccache my_cc'345 env['CXX'] = 'my_cxx'346 del env['LDSHARED']347 sysconfig.customize_compiler(self.cc)348 assert self.cc.linker_so[0:2] == ['ccache', 'my_cc']349 self.cc.link(None, [], 'a.out', target_lang='c++')350 call_args = mock_spawn.call_args[0][0]351 expected = ['my_cxx', '-bundle', '-undefined', 'dynamic_lookup']352 assert call_args[:4] == expected353 354 @pytest.mark.skipif('platform.system == "Windows"')355 def test_explicit_ldshared(self):356 # Issue #18080:357 # ensure that setting CC env variable does not change358 # explicit LDSHARED setting for linker359 def gcv(v):360 if v == 'LDSHARED':361 return 'gcc-4.2 -bundle -undefined dynamic_lookup '362 return 'gcc-4.2'363 364 def gcvs(*args, _orig=sysconfig.get_config_vars):365 if args:366 return list(map(sysconfig.get_config_var, args))367 return _orig()368 369 sysconfig.get_config_var = gcv370 sysconfig.get_config_vars = gcvs371 with EnvironmentVarGuard() as env:372 env['CC'] = 'my_cc'373 env['LDSHARED'] = 'my_ld -bundle -dynamic'374 sysconfig.customize_compiler(self.cc)375 assert self.cc.linker_so[0] == 'my_ld'376 377 def test_has_function(self):378 # Issue https://github.com/pypa/distutils/issues/64:379 # ensure that setting output_dir does not raise380 # FileNotFoundError: [Errno 2] No such file or directory: 'a.out'381 self.cc.output_dir = 'scratch'382 os.chdir(self.mkdtemp())383 self.cc.has_function('abort')384 385 def test_find_library_file(self, monkeypatch):386 compiler = unix.Compiler()387 compiler._library_root = lambda dir: dir388 monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)389 390 libname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll'391 dirs = ('/foo/bar/missing', '/foo/bar/existing')392 assert (393 compiler.find_library_file(dirs, 'abc').replace('\\', '/')394 == f'/foo/bar/existing/{libname}'395 )396 assert (397 compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')398 == f'/foo/bar/existing/{libname}'399 )400 401 monkeypatch.setattr(402 os.path,403 'exists',404 lambda d: 'existing' in d and '.a' in d and '.dll.a' not in d,405 )406 assert (407 compiler.find_library_file(dirs, 'abc').replace('\\', '/')408 == '/foo/bar/existing/libabc.a'409 )410 assert (411 compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')412 == '/foo/bar/existing/libabc.a'413 )414 