@@ -150,7 +150,9 @@ def sysroot_paths(make_vars, subdirs):
150
150
break
151
151
return dirs
152
152
153
+
153
154
MACOS_SDK_ROOT = None
155
+ MACOS_SDK_SPECIFIED = None
154
156
155
157
def macosx_sdk_root ():
156
158
"""Return the directory of the current macOS SDK.
@@ -162,8 +164,9 @@ def macosx_sdk_root():
162
164
(The SDK may be supplied via Xcode or via the Command Line Tools).
163
165
The SDK paths used by Apple-supplied tool chains depend on the
164
166
setting of various variables; see the xcrun man page for more info.
167
+ Also sets MACOS_SDK_SPECIFIED for use by macosx_sdk_specified().
165
168
"""
166
- global MACOS_SDK_ROOT
169
+ global MACOS_SDK_ROOT , MACOS_SDK_SPECIFIED
167
170
168
171
# If already called, return cached result.
169
172
if MACOS_SDK_ROOT :
@@ -173,8 +176,10 @@ def macosx_sdk_root():
173
176
m = re .search (r'-isysroot\s*(\S+)' , cflags )
174
177
if m is not None :
175
178
MACOS_SDK_ROOT = m .group (1 )
179
+ MACOS_SDK_SPECIFIED = MACOS_SDK_ROOT != '/'
176
180
else :
177
181
MACOS_SDK_ROOT = '/'
182
+ MACOS_SDK_SPECIFIED = False
178
183
cc = sysconfig .get_config_var ('CC' )
179
184
tmpfile = '/tmp/setup_sdk_root.%d' % os .getpid ()
180
185
try :
@@ -203,6 +208,28 @@ def macosx_sdk_root():
203
208
return MACOS_SDK_ROOT
204
209
205
210
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
+
206
233
def is_macosx_sdk_path (path ):
207
234
"""
208
235
Returns True if 'path' can be located in an OSX SDK
@@ -1830,31 +1857,73 @@ def detect_tkinter_explicitly(self):
1830
1857
return True
1831
1858
1832
1859
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
+
1835
1898
from os .path import join , exists
1836
- framework_dirs = [
1837
- '/Library/Frameworks' ,
1838
- '/System/Library/Frameworks/' ,
1839
- join (os .getenv ('HOME' ), '/Library/Frameworks' )
1840
- ]
1841
1899
1842
- sysroot = macosx_sdk_root ()
1900
+ sysroot = macosx_sdk_root () # path to the SDK or '/'
1843
1901
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.
1847
1922
for F in framework_dirs :
1848
1923
# both Tcl.framework and Tk.framework should be present
1849
-
1850
-
1851
1924
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
1858
1927
else :
1859
1928
# ok, F is now directory with both frameworks. Continure
1860
1929
# building
@@ -1864,38 +1933,26 @@ def detect_tkinter_darwin(self):
1864
1933
# will now resume.
1865
1934
return False
1866
1935
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
- #
1871
1936
include_dirs = [
1872
1937
join (F , fw + '.framework' , H )
1873
1938
for fw in ('Tcl' , 'Tk' )
1874
- for H in ('Headers' , 'Versions/Current/PrivateHeaders' )
1939
+ for H in ('Headers' ,)
1875
1940
]
1876
1941
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 ]
1882
1944
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.
1885
1946
cflags = sysconfig .get_config_vars ('CFLAGS' )[0 ]
1886
1947
archs = re .findall (r'-arch\s+(\w+)' , cflags )
1887
1948
1888
1949
tmpfile = os .path .join (self .build_temp , 'tk.arch' )
1889
1950
if not os .path .exists (self .build_temp ):
1890
1951
os .makedirs (self .build_temp )
1891
1952
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
+ )
1899
1956
with open (tmpfile ) as fp :
1900
1957
detected_archs = []
1901
1958
for ln in fp :
@@ -1904,16 +1961,26 @@ def detect_tkinter_darwin(self):
1904
1961
detected_archs .append (ln .split ()[- 1 ])
1905
1962
os .unlink (tmpfile )
1906
1963
1964
+ arch_args = []
1907
1965
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' )
1910
1977
1911
1978
self .add (Extension ('_tkinter' , ['_tkinter.c' , 'tkappinit.c' ],
1912
1979
define_macros = [('WITH_APPINIT' , 1 )],
1913
1980
include_dirs = include_dirs ,
1914
1981
libraries = [],
1915
- extra_compile_args = frameworks [ 2 :] ,
1916
- extra_link_args = frameworks ))
1982
+ extra_compile_args = compile_args ,
1983
+ extra_link_args = link_args ))
1917
1984
return True
1918
1985
1919
1986
def detect_tkinter (self ):
0 commit comments