dist: Add support for tkinter

Requires adding `tkinter` to `requirements.txt`. For now, wheels are only provided for Python 3.6 and up.

Fixes #780
This commit is contained in:
rdb 2022-01-04 15:20:28 +01:00
parent 210f7aecfb
commit 6935d2badc
2 changed files with 45 additions and 20 deletions

View File

@ -2332,7 +2332,13 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
return None
try:
fp = zip.open(fn.replace(os.path.sep, '/'), 'r')
zip_fn = fn.replace(os.path.sep, '/')
if zip_fn.startswith('deploy_libs/_tkinter.'):
# If we have a tkinter wheel on the path, ignore the
# _tkinter extension in deploy-libs.
if any(entry.endswith(".whl") and os.path.basename(entry).startswith("tkinter-") for entry in self.path):
return None
fp = zip.open(zip_fn, 'r')
except KeyError:
return None

View File

@ -114,6 +114,7 @@ PACKAGE_DATA_DIRS = {
],
'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
'certifi': [('certifi/cacert.pem', '', {})],
'_tkinter_ext': [('_tkinter_ext/tcl/**', 'tcl', {})],
}
# Some dependencies have extra directories that need to be scanned for DLLs.
@ -147,21 +148,6 @@ import __builtin__
__builtin__.__import__ = __import__
__builtin__.__file__ = sys.executable
del __builtin__
# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
import os
tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
if os.path.isdir(tcl_dir):
for dir in os.listdir(tcl_dir):
sub_dir = os.path.join(tcl_dir, dir)
if os.path.isdir(sub_dir):
if dir.startswith('tcl'):
os.environ['TCL_LIBRARY'] = sub_dir
if dir.startswith('tk'):
os.environ['TK_LIBRARY'] = sub_dir
if dir.startswith('tix'):
os.environ['TIX_LIBRARY'] = sub_dir
del os
"""
# site.py for Python 3.
@ -196,7 +182,11 @@ def get_data(path):
FrozenImporter.find_spec = find_spec
FrozenImporter.get_data = get_data
"""
# This addendum is only needed for legacy tkinter handling, since the new
# tkinter package already contains this logic.
SITE_PY_TKINTER_ADDENDUM = """
# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
import os
tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
@ -204,7 +194,7 @@ if os.path.isdir(tcl_dir):
for dir in os.listdir(tcl_dir):
sub_dir = os.path.join(tcl_dir, dir)
if os.path.isdir(sub_dir):
if dir.startswith('tcl'):
if dir.startswith('tcl') and os.path.isfile(os.path.join(sub_dir, 'init.tcl')):
os.environ['TCL_LIBRARY'] = sub_dir
if dir.startswith('tk'):
os.environ['TK_LIBRARY'] = sub_dir
@ -592,6 +582,7 @@ class build_apps(setuptools.Command):
path = sys.path[:]
p3dwhl = None
wheelpaths = []
has_tkinter_wheel = False
if use_wheels:
wheelpaths = self.download_wheels(platform)
@ -601,6 +592,8 @@ class build_apps(setuptools.Command):
p3dwhlfn = whl
p3dwhl = self._get_zip_file(p3dwhlfn)
break
elif os.path.basename(whl).startswith('tkinter-'):
has_tkinter_wheel = True
else:
raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform))
@ -613,6 +606,11 @@ class build_apps(setuptools.Command):
distutils.log.WARN
)
for whl in wheelpaths:
if os.path.basename(whl).startswith('tkinter-'):
has_tkinter_wheel = True
break
#whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths}
# Add whl files to the path so they are picked up by modulefinder
@ -756,9 +754,14 @@ class build_apps(setuptools.Command):
return search_path
def create_runtime(appname, mainscript, use_console):
site_py = SITE_PY
if not has_tkinter_wheel:
# Legacy handling for Tcl data files
site_py += SITE_PY_TKINTER_ADDENDUM
freezer = FreezeTool.Freezer(platform=platform, path=path)
freezer.addModule('__main__', filename=mainscript)
freezer.addModule('site', filename='site.py', text=SITE_PY)
freezer.addModule('site', filename='site.py', text=site_py)
for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []):
freezer.addModule(incmod)
for exmod in self.exclude_modules.get(appname, []) + self.exclude_modules.get('*', []):
@ -835,6 +838,12 @@ class build_apps(setuptools.Command):
for appname, scriptname in self.console_apps.items():
create_runtime(appname, scriptname, True)
# Warn if tkinter is used but hasn't been added to requirements.txt
if not has_tkinter_wheel and '_tkinter' in freezer_modules:
# The on-windows-for-windows case is handled as legacy below
if sys.platform != "win32" or not platform.startswith('win'):
self.warn("Detected use of tkinter, but tkinter is not specified in requirements.txt!")
# Copy extension modules
whl_modules = []
whl_modules_ext = ''
@ -849,6 +858,11 @@ class build_apps(setuptools.Command):
if not any(i.endswith(suffix) for suffix in ext_suffixes):
continue
if has_tkinter_wheel and i.startswith('deploy_libs/_tkinter.'):
# Ignore this one, we have a separate tkinter package
# nowadays that contains all the dependencies.
continue
base = os.path.basename(i)
module, _, ext = base.partition('.')
whl_modules.append(module)
@ -917,8 +931,8 @@ class build_apps(setuptools.Command):
self.copy_with_dependencies(source_path, target_path, search_path)
# Copy over the tcl directory.
#TODO: get this to work on non-Windows platforms.
if sys.platform == "win32" and platform.startswith('win'):
# This is legacy, we nowadays recommend the separate tkinter wheel.
if sys.platform == "win32" and platform.startswith('win') and not has_tkinter_wheel:
tcl_dir = os.path.join(sys.prefix, 'tcl')
tkinter_name = 'tkinter' if sys.version_info >= (3, 0) else 'Tkinter'
@ -952,9 +966,14 @@ class build_apps(setuptools.Command):
target_dir = os.path.join(builddir, target_dir)
for wf in filenames:
if wf.endswith('/'):
# Skip directories.
continue
if wf.lower().startswith(source_dir.lower() + '/'):
if not srcglob.matches(wf.lower()):
continue
wf = wf.replace('/', os.sep)
relpath = wf[len(source_dir) + 1:]
source_path = os.path.join(whl, wf)