@@ -150,7 +150,9 @@ def sysroot_paths(make_vars, subdirs):
150150 break
151151 return dirs
152152
153+
153154MACOS_SDK_ROOT = None
155+ MACOS_SDK_SPECIFIED = None
154156
155157def 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+
206233def 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