Skip to content

Commit 1731d6d

Browse files
authored
bpo-34956: Fix macOS _tkinter use of Tcl/Tk in /Library/Frameworks (GH-20171)
_tkinter now builds and links with non-system Tcl and Tk frameworks if they are installed in /Library/Frameworks as had been the case on older releases of macOS. If a macOS SDK is explicitly configured, by using ./configure --enable-universalsdk= or -isysroot, only a Library/Frameworks directory in the SDK itself is searched. The default behavior can still be overridden with configure --with-tcltk-includes and --with-tcltk-libs.
1 parent 58205a0 commit 1731d6d

File tree

2 files changed

+116
-43
lines changed

2 files changed

+116
-43
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
_tkinter now builds and links with non-system Tcl and Tk frameworks if they
2+
are installed in /Library/Frameworks as had been the case on older releases
3+
of macOS. If a macOS SDK is explicitly configured, by using ./configure
4+
--enable-universalsdk= or -isysroot, only a Library/Frameworks directory in
5+
the SDK itself is searched. The default behavior can still be overridden with
6+
configure --with-tcltk-includes and --with-tcltk-libs.

setup.py

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ def sysroot_paths(make_vars, subdirs):
150150
break
151151
return dirs
152152

153+
153154
MACOS_SDK_ROOT = None
155+
MACOS_SDK_SPECIFIED = None
154156

155157
def macosx_sdk_root():
156158
"""Return the directory of the current macOS SDK.
@@ -162,8 +164,9 @@ def macosx_sdk_root():
162164
(The SDK may be supplied via Xcode or via the Command Line Tools).
163165
The SDK paths used by Apple-supplied tool chains depend on the
164166
setting of various variables; see the xcrun man page for more info.
167+
Also sets MACOS_SDK_SPECIFIED for use by macosx_sdk_specified().
165168
"""
166-
global MACOS_SDK_ROOT
169+
global MACOS_SDK_ROOT, MACOS_SDK_SPECIFIED
167170

168171
# If already called, return cached result.
169172
if MACOS_SDK_ROOT:
@@ -173,8 +176,10 @@ def macosx_sdk_root():
173176
m = re.search(r'-isysroot\s*(\S+)', cflags)
174177
if m is not None:
175178
MACOS_SDK_ROOT = m.group(1)
179+
MACOS_SDK_SPECIFIED = MACOS_SDK_ROOT != '/'
176180
else:
177181
MACOS_SDK_ROOT = '/'
182+
MACOS_SDK_SPECIFIED = False
178183
cc = sysconfig.get_config_var('CC')
179184
tmpfile = '/tmp/setup_sdk_root.%d' % os.getpid()
180185
try:
@@ -203,6 +208,28 @@ def macosx_sdk_root():
203208
return MACOS_SDK_ROOT
204209

205210

211+
def macosx_sdk_specified():
212+
"""Returns true if an SDK was explicitly configured.
213+
214+
True if an SDK was selected at configure time, either by specifying
215+
--enable-universalsdk=(something other than no or /) or by adding a
216+
-isysroot option to CFLAGS. In some cases, like when making
217+
decisions about macOS Tk framework paths, we need to be able to
218+
know whether the user explicitly asked to build with an SDK versus
219+
the implicit use of an SDK when header files are no longer
220+
installed on a running system by the Command Line Tools.
221+
"""
222+
global MACOS_SDK_SPECIFIED
223+
224+
# If already called, return cached result.
225+
if MACOS_SDK_SPECIFIED:
226+
return MACOS_SDK_SPECIFIED
227+
228+
# Find the sdk root and set MACOS_SDK_SPECIFIED
229+
macosx_sdk_root()
230+
return MACOS_SDK_SPECIFIED
231+
232+
206233
def is_macosx_sdk_path(path):
207234
"""
208235
Returns True if 'path' can be located in an OSX SDK
@@ -1830,31 +1857,73 @@ def detect_tkinter_explicitly(self):
18301857
return True
18311858

18321859
def detect_tkinter_darwin(self):
1833-
# The _tkinter module, using frameworks. Since frameworks are quite
1834-
# different the UNIX search logic is not sharable.
1860+
# Build default _tkinter on macOS using Tcl and Tk frameworks.
1861+
#
1862+
# The macOS native Tk (AKA Aqua Tk) and Tcl are most commonly
1863+
# built and installed as macOS framework bundles. However,
1864+
# for several reasons, we cannot take full advantage of the
1865+
# Apple-supplied compiler chain's -framework options here.
1866+
# Instead, we need to find and pass to the compiler the
1867+
# absolute paths of the Tcl and Tk headers files we want to use
1868+
# and the absolute path to the directory containing the Tcl
1869+
# and Tk frameworks for linking.
1870+
#
1871+
# We want to handle here two common use cases on macOS:
1872+
# 1. Build and link with system-wide third-party or user-built
1873+
# Tcl and Tk frameworks installed in /Library/Frameworks.
1874+
# 2. Build and link using a user-specified macOS SDK so that the
1875+
# built Python can be exported to other systems. In this case,
1876+
# search only the SDK's /Library/Frameworks (normally empty)
1877+
# and /System/Library/Frameworks.
1878+
#
1879+
# Any other use case should be able to be handled explicitly by
1880+
# using the options described above in detect_tkinter_explicitly().
1881+
# In particular it would be good to handle here the case where
1882+
# you want to build and link with a framework build of Tcl and Tk
1883+
# that is not in /Library/Frameworks, say, in your private
1884+
# $HOME/Library/Frameworks directory or elsewhere. It turns
1885+
# out to be difficult to make that work automtically here
1886+
# without bringing into play more tools and magic. That case
1887+
# can be hamdled using a recipe with the right arguments
1888+
# to detect_tkinter_explicitly().
1889+
#
1890+
# Note also that the fallback case here is to try to use the
1891+
# Apple-supplied Tcl and Tk frameworks in /System/Library but
1892+
# be forewarned that they are deprecated by Apple and typically
1893+
# out-of-date and buggy; their use should be avoided if at
1894+
# all possible by installing a newer version of Tcl and Tk in
1895+
# /Library/Frameworks before bwfore building Python without
1896+
# an explicit SDK or by configuring build arguments explicitly.
1897+
18351898
from os.path import join, exists
1836-
framework_dirs = [
1837-
'/Library/Frameworks',
1838-
'/System/Library/Frameworks/',
1839-
join(os.getenv('HOME'), '/Library/Frameworks')
1840-
]
18411899

1842-
sysroot = macosx_sdk_root()
1900+
sysroot = macosx_sdk_root() # path to the SDK or '/'
18431901

1844-
# Find the directory that contains the Tcl.framework and Tk.framework
1845-
# bundles.
1846-
# XXX distutils should support -F!
1902+
if macosx_sdk_specified():
1903+
# Use case #2: an SDK other than '/' was specified.
1904+
# Only search there.
1905+
framework_dirs = [
1906+
join(sysroot, 'Library', 'Frameworks'),
1907+
join(sysroot, 'System', 'Library', 'Frameworks'),
1908+
]
1909+
else:
1910+
# Use case #1: no explicit SDK selected.
1911+
# Search the local system-wide /Library/Frameworks,
1912+
# not the one in the default SDK, othewise fall back to
1913+
# /System/Library/Frameworks whose header files may be in
1914+
# the default SDK or, on older systems, actually installed.
1915+
framework_dirs = [
1916+
join('/', 'Library', 'Frameworks'),
1917+
join(sysroot, 'System', 'Library', 'Frameworks'),
1918+
]
1919+
1920+
# Find the directory that contains the Tcl.framework and
1921+
# Tk.framework bundles.
18471922
for F in framework_dirs:
18481923
# both Tcl.framework and Tk.framework should be present
1849-
1850-
18511924
for fw in 'Tcl', 'Tk':
1852-
if is_macosx_sdk_path(F):
1853-
if not exists(join(sysroot, F[1:], fw + '.framework')):
1854-
break
1855-
else:
1856-
if not exists(join(F, fw + '.framework')):
1857-
break
1925+
if not exists(join(F, fw + '.framework')):
1926+
break
18581927
else:
18591928
# ok, F is now directory with both frameworks. Continure
18601929
# building
@@ -1864,38 +1933,26 @@ def detect_tkinter_darwin(self):
18641933
# will now resume.
18651934
return False
18661935

1867-
# For 8.4a2, we must add -I options that point inside the Tcl and Tk
1868-
# frameworks. In later release we should hopefully be able to pass
1869-
# the -F option to gcc, which specifies a framework lookup path.
1870-
#
18711936
include_dirs = [
18721937
join(F, fw + '.framework', H)
18731938
for fw in ('Tcl', 'Tk')
1874-
for H in ('Headers', 'Versions/Current/PrivateHeaders')
1939+
for H in ('Headers',)
18751940
]
18761941

1877-
# For 8.4a2, the X11 headers are not included. Rather than include a
1878-
# complicated search, this is a hard-coded path. It could bail out
1879-
# if X11 libs are not found...
1880-
include_dirs.append('/usr/X11R6/include')
1881-
frameworks = ['-framework', 'Tcl', '-framework', 'Tk']
1942+
# Add the base framework directory as well
1943+
compile_args = ['-F', F]
18821944

1883-
# All existing framework builds of Tcl/Tk don't support 64-bit
1884-
# architectures.
1945+
# Do not build tkinter for archs that this Tk was not built with.
18851946
cflags = sysconfig.get_config_vars('CFLAGS')[0]
18861947
archs = re.findall(r'-arch\s+(\w+)', cflags)
18871948

18881949
tmpfile = os.path.join(self.build_temp, 'tk.arch')
18891950
if not os.path.exists(self.build_temp):
18901951
os.makedirs(self.build_temp)
18911952

1892-
# Note: cannot use os.popen or subprocess here, that
1893-
# requires extensions that are not available here.
1894-
if is_macosx_sdk_path(F):
1895-
run_command("file %s/Tk.framework/Tk | grep 'for architecture' > %s"%(os.path.join(sysroot, F[1:]), tmpfile))
1896-
else:
1897-
run_command("file %s/Tk.framework/Tk | grep 'for architecture' > %s"%(F, tmpfile))
1898-
1953+
run_command(
1954+
"file {}/Tk.framework/Tk | grep 'for architecture' > {}".format(F, tmpfile)
1955+
)
18991956
with open(tmpfile) as fp:
19001957
detected_archs = []
19011958
for ln in fp:
@@ -1904,16 +1961,26 @@ def detect_tkinter_darwin(self):
19041961
detected_archs.append(ln.split()[-1])
19051962
os.unlink(tmpfile)
19061963

1964+
arch_args = []
19071965
for a in detected_archs:
1908-
frameworks.append('-arch')
1909-
frameworks.append(a)
1966+
arch_args.append('-arch')
1967+
arch_args.append(a)
1968+
1969+
compile_args += arch_args
1970+
link_args = [','.join(['-Wl', '-F', F, '-framework', 'Tcl', '-framework', 'Tk']), *arch_args]
1971+
1972+
# The X11/xlib.h file bundled in the Tk sources can cause function
1973+
# prototype warnings from the compiler. Since we cannot easily fix
1974+
# that, suppress the warnings here instead.
1975+
if '-Wstrict-prototypes' in cflags.split():
1976+
compile_args.append('-Wno-strict-prototypes')
19101977

19111978
self.add(Extension('_tkinter', ['_tkinter.c', 'tkappinit.c'],
19121979
define_macros=[('WITH_APPINIT', 1)],
19131980
include_dirs=include_dirs,
19141981
libraries=[],
1915-
extra_compile_args=frameworks[2:],
1916-
extra_link_args=frameworks))
1982+
extra_compile_args=compile_args,
1983+
extra_link_args=link_args))
19171984
return True
19181985

19191986
def detect_tkinter(self):

0 commit comments

Comments
 (0)