Issue #15663: OS X installer builtin Tcl/Tk support

Make it easier for users to make use of the backup _tkinter linked
with the third-party Tcl and Tk frameworks in /Library/Frameworks.
The two tkinter variants are now installed in separate directories
under a new lib-tkinter.  This allows per-user selection by
manipulating sys.path, directly or with PYTHONPATH.  If this
proves useful, we can supply a more convenient user interface
to supply the paths.  For now, this remains somewhat experimental.
This commit is contained in:
Ned Deily 2013-10-26 03:16:44 -07:00
parent 2ff68dd526
commit 050fcd51cc
2 changed files with 51 additions and 19 deletions

View File

@ -68,28 +68,28 @@ for each release.
- requires ActiveState Tcl/Tk 8.5.14 (or later) to be installed for building - requires ActiveState Tcl/Tk 8.5.14 (or later) to be installed for building
* Beginning with Python 3.3.3, this installer now includes its own * Beginning with Python 3.3.3, this installer now includes its own
private copy of Tcl and Tk 8.5.15 libraries and thus is no longer builtin copy of Tcl and Tk 8.5.15 libraries and thus is no longer
dependent on the buggy releases of Aqua Cocoa Tk 8.5 shipped with dependent on the buggy releases of Aqua Cocoa Tk 8.5 shipped with
OS X 10.6 or on installing a newer third-party version of Tcl/Tk OS X 10.6 or on installing a newer third-party version of Tcl/Tk
in /Library/Frameworks, such as from ActiveState. If it is in /Library/Frameworks, such as from ActiveState. If it is
necessary to fallback to using a third-party Tcl/Tk because of necessary to fallback to using a third-party Tcl/Tk because of
a problem with the private Tcl/Tk, there is a backup version of a problem with the builtin Tcl/Tk, there is a backup version of
the _tkinter extension included which will dynamically link to the _tkinter extension included which will dynamically link to
Tcl and Tk frameworks in /Library/Frameworks as in previous releases. Tcl and Tk frameworks in /Library/Frameworks as in previous releases.
To enable (for all users of this Python 3.3):: To enable (for all users of this Python 3.3)::
sudo bash sudo bash
cd /Library/Frameworks/Python.framework/Versions/3.3 cd /Library/Frameworks/Python.framework/Versions/3.3
cd ./lib/python3.3/lib-dynload cd ./lib/python3.3
cp -p _tkinter.so.framework _tkinter.so cp -p ./lib-tkinter/library/_tkinter.so ./lib-dynload
exit exit
To restore using Python's private versions of Tcl and Tk:: To restore using Python's builtin versions of Tcl and Tk::
sudo bash sudo bash
cd /Library/Frameworks/Python.framework/Versions/3.3 cd /Library/Frameworks/Python.framework/Versions/3.3
cd ./lib/python3.3/lib-dynload cd ./lib/python3.3
cp -p _tkinter.so.private _tkinter.so cp -p ./lib-tkinter/builtin/_tkinter.so ./lib-dynload
exit exit
- recommended build environment: - recommended build environment:

View File

@ -565,11 +565,11 @@ def checkEnvironment():
] ]
# For 10.6+ builds, we build two versions of _tkinter: # For 10.6+ builds, we build two versions of _tkinter:
# - the traditional version (renamed to _tkinter.so.framework) linked # - the traditional version (renamed to _tkinter_library.so) linked
# with /Library/Frameworks/{Tcl,Tk}.framework # with /Library/Frameworks/{Tcl,Tk}.framework
# - the default version linked with our private copies of Tcl and Tk # - the default version linked with our builtin copies of Tcl and Tk
if DEPTARGET > '10.5': if DEPTARGET > '10.5':
EXPECTED_SHARED_LIBS['_tkinter.so.framework'] = \ EXPECTED_SHARED_LIBS['_tkinter_library.so'] = \
EXPECTED_SHARED_LIBS['_tkinter.so'] EXPECTED_SHARED_LIBS['_tkinter.so']
EXPECTED_SHARED_LIBS['_tkinter.so'] = [ EXPECTED_SHARED_LIBS['_tkinter.so'] = [
"/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib" "/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib"
@ -966,18 +966,18 @@ def buildPython():
# of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is # of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is
# out-of-date and has critical bugs. Save the _tkinter.so that was # out-of-date and has critical bugs. Save the _tkinter.so that was
# linked with /Library/Frameworks/{Tck,Tk}.framework and build # linked with /Library/Frameworks/{Tck,Tk}.framework and build
# another _tkinter.so linked with our private Tcl and Tk libs. # another _tkinter.so linked with our builtin Tcl and Tk libs.
if DEPTARGET > '10.5': if DEPTARGET > '10.5':
runCommand("find build -name '_tkinter.so' " runCommand("find build -name '_tkinter.so' "
" -execdir mv '{}' '{}'.framework \;") " -execdir mv '{}' _tkinter_library.so \;")
print("Running make to rebuild _tkinter") print("Running make to build builtin _tkinter")
runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' " runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' "
"TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%( "TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%(
shellQuote(WORKDIR)[1:-1], shellQuote(WORKDIR)[1:-1],
shellQuote(WORKDIR)[1:-1])) shellQuote(WORKDIR)[1:-1]))
# make a backup copy, just in case # make a copy which will be moved to lib-tkinter later
runCommand("find build -name '_tkinter.so' " runCommand("find build -name '_tkinter.so' "
" -execdir cp -p '{}' '{}'.private \;") " -execdir cp -p '{}' _tkinter_builtin.so \;")
print("Running make install") print("Running make install")
runCommand("make install DESTDIR=%s"%( runCommand("make install DESTDIR=%s"%(
@ -999,11 +999,31 @@ def buildPython():
'Python.framework', 'Versions', getVersion(), 'Python.framework', 'Versions', getVersion(),
'lib')))) 'lib'))))
path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
'Python.framework', 'Versions',
version, 'lib', 'python%s'%(version,))
# If we made multiple versions of _tkinter, move them to
# their own directories under python lib. This allows
# users to select which to import by manipulating sys.path
# directly or with PYTHONPATH.
if DEPTARGET > '10.5':
TKINTERS = ['builtin', 'library']
tkinter_moves = [('_tkinter_' + tkn + '.so',
os.path.join(path_to_lib, 'lib-tkinter', tkn))
for tkn in TKINTERS]
# Create the destination directories under lib-tkinter.
# The permissions and uid/gid will be fixed up next.
for tkm in tkinter_moves:
os.makedirs(tkm[1])
print("Fix file modes") print("Fix file modes")
frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework') frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
gid = grp.getgrnam('admin').gr_gid gid = grp.getgrnam('admin').gr_gid
shared_lib_error = False shared_lib_error = False
moves_list = []
for dirpath, dirnames, filenames in os.walk(frmDir): for dirpath, dirnames, filenames in os.walk(frmDir):
for dn in dirnames: for dn in dirnames:
os.chmod(os.path.join(dirpath, dn), STAT_0o775) os.chmod(os.path.join(dirpath, dn), STAT_0o775)
@ -1029,9 +1049,25 @@ def buildPython():
% (sl, p)) % (sl, p))
shared_lib_error = True shared_lib_error = True
# If this is a _tkinter variant, move it to its own directory
# now that we have fixed its permissions and checked that it
# was linked properly. The directory was created earlier.
# The files are moved after the entire tree has been walked
# since the shared library checking depends on the files
# having unique names.
if DEPTARGET > '10.5':
for tkm in tkinter_moves:
if fn == tkm[0]:
moves_list.append(
(p, os.path.join(tkm[1], '_tkinter.so')))
if shared_lib_error: if shared_lib_error:
fatal("Unexpected shared library errors.") fatal("Unexpected shared library errors.")
# Now do the moves.
for ml in moves_list:
shutil.move(ml[0], ml[1])
if PYTHON_3: if PYTHON_3:
LDVERSION=None LDVERSION=None
VERSION=None VERSION=None
@ -1061,10 +1097,6 @@ def buildPython():
include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,) include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,) lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
'Python.framework', 'Versions',
version, 'lib', 'python%s'%(version,))
# fix Makefile # fix Makefile
path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile') path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
fp = open(path, 'r') fp = open(path, 'r')