Skip to content

Commit 8f53bf3

Browse files
authored
Merge pull request #37 from acolinisi/PR--rpath-on-macOS
distutils: pass -rpath on macOS when requested
2 parents fbfaff7 + 221e871 commit 8f53bf3

File tree

4 files changed

+142
-32
lines changed

4 files changed

+142
-32
lines changed

distutils/spawn.py

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
from distutils import log
1616

1717

18-
if sys.platform == 'darwin':
19-
_cfg_target = None
20-
_cfg_target_split = None
21-
22-
2318
def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
2419
"""Run another program, specified as a command list 'cmd', in a new process.
2520
@@ -52,28 +47,10 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
5247
env = env if env is not None else dict(os.environ)
5348

5449
if sys.platform == 'darwin':
55-
global _cfg_target, _cfg_target_split
56-
if _cfg_target is None:
57-
from distutils import sysconfig
58-
_cfg_target = sysconfig.get_config_var(
59-
'MACOSX_DEPLOYMENT_TARGET') or ''
60-
if _cfg_target:
61-
_cfg_target_split = [int(x) for x in _cfg_target.split('.')]
62-
if _cfg_target:
63-
# Ensure that the deployment target of the build process is not
64-
# less than 10.3 if the interpreter was built for 10.3 or later.
65-
# This ensures extension modules are built with correct
66-
# compatibility values, specifically LDSHARED which can use
67-
# '-undefined dynamic_lookup' which only works on >= 10.3.
68-
cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
69-
cur_target_split = [int(x) for x in cur_target.split('.')]
70-
if _cfg_target_split[:2] >= [10, 3] and cur_target_split[:2] < [10, 3]:
71-
my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
72-
'now "%s" but "%s" during configure;'
73-
'must use 10.3 or later'
74-
% (cur_target, _cfg_target))
75-
raise DistutilsPlatformError(my_msg)
76-
env.update(MACOSX_DEPLOYMENT_TARGET=cur_target)
50+
from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver
51+
macosx_target_ver = get_macosx_target_ver()
52+
if macosx_target_ver:
53+
env[MACOSX_VERSION_VAR] = macosx_target_ver
7754

7855
try:
7956
proc = subprocess.Popen(cmd, env=env)

distutils/tests/test_unixccompiler.py

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""Tests for distutils.unixccompiler."""
2+
import os
23
import sys
34
import unittest
45
from test.support import run_unittest
56

67
from .py38compat import EnvironmentVarGuard
78

89
from distutils import sysconfig
10+
from distutils.errors import DistutilsPlatformError
911
from distutils.unixccompiler import UnixCCompiler
12+
from distutils.util import _clear_cached_macosx_ver
1013

1114
class UnixCCompilerTestCase(unittest.TestCase):
1215

@@ -26,18 +29,90 @@ def tearDown(self):
2629

2730
@unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
2831
def test_runtime_libdir_option(self):
29-
# Issue#5900
32+
# Issue #5900; GitHub Issue #37
3033
#
3134
# Ensure RUNPATH is added to extension modules with RPATH if
3235
# GNU ld is used
3336

3437
# darwin
3538
sys.platform = 'darwin'
36-
self.assertEqual(self.cc.rpath_foo(), '-L/foo')
39+
darwin_ver_var = 'MACOSX_DEPLOYMENT_TARGET'
40+
darwin_rpath_flag = '-Wl,-rpath,/foo'
41+
darwin_lib_flag = '-L/foo'
42+
43+
# (macOS version from syscfg, macOS version from env var) -> flag
44+
# Version value of None generates two tests: as None and as empty string
45+
# Expected flag value of None means an mismatch exception is expected
46+
darwin_test_cases = [
47+
((None , None ), darwin_lib_flag),
48+
((None , '11' ), darwin_rpath_flag),
49+
(('10' , None ), darwin_lib_flag),
50+
(('10.3' , None ), darwin_lib_flag),
51+
(('10.3.1', None ), darwin_lib_flag),
52+
(('10.5' , None ), darwin_rpath_flag),
53+
(('10.5.1', None ), darwin_rpath_flag),
54+
(('10.3' , '10.3' ), darwin_lib_flag),
55+
(('10.3' , '10.5' ), darwin_rpath_flag),
56+
(('10.5' , '10.3' ), darwin_lib_flag),
57+
(('10.5' , '11' ), darwin_rpath_flag),
58+
(('10.4' , '10' ), None),
59+
]
60+
61+
def make_darwin_gcv(syscfg_macosx_ver):
62+
def gcv(var):
63+
if var == darwin_ver_var:
64+
return syscfg_macosx_ver
65+
return "xxx"
66+
return gcv
67+
68+
def do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag):
69+
env = os.environ
70+
msg = "macOS version = (sysconfig=%r, env=%r)" % \
71+
(syscfg_macosx_ver, env_macosx_ver)
72+
73+
# Save
74+
old_gcv = sysconfig.get_config_var
75+
old_env_macosx_ver = env.get(darwin_ver_var)
76+
77+
# Setup environment
78+
_clear_cached_macosx_ver()
79+
sysconfig.get_config_var = make_darwin_gcv(syscfg_macosx_ver)
80+
if env_macosx_ver is not None:
81+
env[darwin_ver_var] = env_macosx_ver
82+
elif darwin_ver_var in env:
83+
env.pop(darwin_ver_var)
84+
85+
# Run the test
86+
if expected_flag is not None:
87+
self.assertEqual(self.cc.rpath_foo(), expected_flag, msg=msg)
88+
else:
89+
with self.assertRaisesRegex(DistutilsPlatformError,
90+
darwin_ver_var + r' mismatch', msg=msg):
91+
self.cc.rpath_foo()
92+
93+
# Restore
94+
if old_env_macosx_ver is not None:
95+
env[darwin_ver_var] = old_env_macosx_ver
96+
elif darwin_ver_var in env:
97+
env.pop(darwin_ver_var)
98+
sysconfig.get_config_var = old_gcv
99+
_clear_cached_macosx_ver()
100+
101+
for macosx_vers, expected_flag in darwin_test_cases:
102+
syscfg_macosx_ver, env_macosx_ver = macosx_vers
103+
do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag)
104+
# Bonus test cases with None interpreted as empty string
105+
if syscfg_macosx_ver is None:
106+
do_darwin_test("", env_macosx_ver, expected_flag)
107+
if env_macosx_ver is None:
108+
do_darwin_test(syscfg_macosx_ver, "", expected_flag)
109+
if syscfg_macosx_ver is None and env_macosx_ver is None:
110+
do_darwin_test("", "", expected_flag)
111+
112+
old_gcv = sysconfig.get_config_var
37113

38114
# hp-ux
39115
sys.platform = 'hp-ux'
40-
old_gcv = sysconfig.get_config_var
41116
def gcv(v):
42117
return 'xxx'
43118
sysconfig.get_config_var = gcv

distutils/unixccompiler.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,12 @@ def runtime_library_dir_option(self, dir):
233233
# we use this hack.
234234
compiler = os.path.basename(sysconfig.get_config_var("CC"))
235235
if sys.platform[:6] == "darwin":
236-
# MacOSX's linker doesn't understand the -R flag at all
237-
return "-L" + dir
236+
from distutils.util import get_macosx_target_ver, split_version
237+
macosx_target_ver = get_macosx_target_ver()
238+
if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:
239+
return "-Wl,-rpath," + dir
240+
else: # no support for -rpath on earlier macOS versions
241+
return "-L" + dir
238242
elif sys.platform[:7] == "freebsd":
239243
return "-Wl,-rpath=" + dir
240244
elif sys.platform[:5] == "hp-ux":

distutils/util.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,60 @@ def get_platform():
108108
else:
109109
return get_host_platform()
110110

111+
112+
if sys.platform == 'darwin':
113+
_syscfg_macosx_ver = None # cache the version pulled from sysconfig
114+
MACOSX_VERSION_VAR = 'MACOSX_DEPLOYMENT_TARGET'
115+
116+
def _clear_cached_macosx_ver():
117+
"""For testing only. Do not call."""
118+
global _syscfg_macosx_ver
119+
_syscfg_macosx_ver = None
120+
121+
def get_macosx_target_ver_from_syscfg():
122+
"""Get the version of macOS latched in the Python interpreter configuration.
123+
Returns the version as a string or None if can't obtain one. Cached."""
124+
global _syscfg_macosx_ver
125+
if _syscfg_macosx_ver is None:
126+
from distutils import sysconfig
127+
ver = sysconfig.get_config_var(MACOSX_VERSION_VAR) or ''
128+
if ver:
129+
_syscfg_macosx_ver = ver
130+
return _syscfg_macosx_ver
131+
132+
def get_macosx_target_ver():
133+
"""Return the version of macOS for which we are building.
134+
135+
The target version defaults to the version in sysconfig latched at time
136+
the Python interpreter was built, unless overriden by an environment
137+
variable. If neither source has a value, then None is returned"""
138+
139+
syscfg_ver = get_macosx_target_ver_from_syscfg()
140+
env_ver = os.environ.get(MACOSX_VERSION_VAR)
141+
142+
if env_ver:
143+
# Validate overriden version against sysconfig version, if have both.
144+
# Ensure that the deployment target of the build process is not less
145+
# than 10.3 if the interpreter was built for 10.3 or later. This
146+
# ensures extension modules are built with correct compatibility
147+
# values, specifically LDSHARED which can use
148+
# '-undefined dynamic_lookup' which only works on >= 10.3.
149+
if syscfg_ver and split_version(syscfg_ver) >= [10, 3] and \
150+
split_version(env_ver) < [10, 3]:
151+
my_msg = ('$' + MACOSX_VERSION_VAR + ' mismatch: '
152+
'now "%s" but "%s" during configure; '
153+
'must use 10.3 or later'
154+
% (env_ver, syscfg_ver))
155+
raise DistutilsPlatformError(my_msg)
156+
return env_ver
157+
return syscfg_ver
158+
159+
160+
def split_version(s):
161+
"""Convert a dot-separated string into a list of numbers for comparisons"""
162+
return [int(n) for n in s.split('.')]
163+
164+
111165
def convert_path (pathname):
112166
"""Return 'pathname' as a name that will work on the native filesystem,
113167
i.e. split it on '/' and put it back together again using the current

0 commit comments

Comments
 (0)