From 7dc9dea7784c2e8cd07af11d1757ae58a3492bd6 Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Fri, 22 May 2015 11:36:53 -0500 Subject: Issue #20035: Reimplement tkinter._fix module as a C function. The new private C function makes no permanent changes to the environment and is #ifdef'd out on non-Windows platforms. --- Doc/whatsnew/3.5.rst | 10 +++- Lib/test/test_tcl.py | 4 +- Lib/test/test_tk.py | 3 -- Lib/test/test_ttk_guionly.py | 4 +- Lib/test/test_ttk_textonly.py | 3 -- Lib/tkinter/__init__.py | 3 -- Lib/tkinter/_fix.py | 78 --------------------------- Misc/NEWS | 4 ++ Modules/_tkinter.c | 119 +++++++++++++++++++++++++++++++++++++++++- PCbuild/_tkinter.vcxproj | 1 + Tools/buildbot/test-amd64.bat | 2 - Tools/buildbot/test.bat | 2 - 12 files changed, 134 insertions(+), 99 deletions(-) delete mode 100644 Lib/tkinter/_fix.py diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index b2571ae..a149417 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -1,4 +1,4 @@ -**************************** +**************************** What's New In Python 3.5 **************************** @@ -659,6 +659,14 @@ time * The :func:`time.monotonic` function is now always available. (Contributed by Victor Stinner in :issue:`22043`.) +tkinter +------- + +* The :module:`tkinter._fix` module used for setting up the Tcl/Tk environment + on Windows has been replaced by a private function in the :module:`_tkinter` + module which makes no permanent changes to environment variables. + (Contributed by Zachary Ware in :issue:`20035`.) + types ----- diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index b612517..5be645a 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -7,9 +7,7 @@ from test import support # Skip this test if the _tkinter module wasn't built. _tkinter = support.import_module('_tkinter') -# Make sure tkinter._fix runs to set up the environment -tkinter = support.import_fresh_module('tkinter') - +import tkinter from tkinter import Tcl from _tkinter import TclError diff --git a/Lib/test/test_tk.py b/Lib/test/test_tk.py index 62729f0..48cefd9 100644 --- a/Lib/test/test_tk.py +++ b/Lib/test/test_tk.py @@ -2,9 +2,6 @@ from test import support # Skip test if _tkinter wasn't built. support.import_module('_tkinter') -# Make sure tkinter._fix runs to set up the environment -support.import_fresh_module('tkinter') - # Skip test if tk cannot be initialized. support.requires('gui') diff --git a/Lib/test/test_ttk_guionly.py b/Lib/test/test_ttk_guionly.py index fcdedac..490e723 100644 --- a/Lib/test/test_ttk_guionly.py +++ b/Lib/test/test_ttk_guionly.py @@ -5,12 +5,10 @@ from test import support # Skip this test if _tkinter wasn't built. support.import_module('_tkinter') -# Make sure tkinter._fix runs to set up the environment -tkinter = support.import_fresh_module('tkinter') - # Skip test if tk cannot be initialized. support.requires('gui') +import tkinter from _tkinter import TclError from tkinter import ttk from tkinter.test import runtktests diff --git a/Lib/test/test_ttk_textonly.py b/Lib/test/test_ttk_textonly.py index 1cfeb15..566fc9d 100644 --- a/Lib/test/test_ttk_textonly.py +++ b/Lib/test/test_ttk_textonly.py @@ -4,9 +4,6 @@ from test import support # Skip this test if _tkinter does not exist. support.import_module('_tkinter') -# Make sure tkinter._fix runs to set up the environment -support.import_fresh_module('tkinter') - from tkinter.test import runtktests def test_main(): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 196809b..ea747ac 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -31,9 +31,6 @@ tk.mainloop() """ import sys -if sys.platform == "win32": - # Attempt to configure Tcl/Tk without requiring PATH - from tkinter import _fix import _tkinter # If this fails your Python may not be configured for Tk TclError = _tkinter.TclError diff --git a/Lib/tkinter/_fix.py b/Lib/tkinter/_fix.py deleted file mode 100644 index fa88734..0000000 --- a/Lib/tkinter/_fix.py +++ /dev/null @@ -1,78 +0,0 @@ -import sys, os - -# Delay import _tkinter until we have set TCL_LIBRARY, -# so that Tcl_FindExecutable has a chance to locate its -# encoding directory. - -# Unfortunately, we cannot know the TCL_LIBRARY directory -# if we don't know the tcl version, which we cannot find out -# without import Tcl. Fortunately, Tcl will itself look in -# \..\tcl, so anything close to -# the real Tcl library will do. - -# Expand symbolic links on Vista -try: - import ctypes - ctypes.windll.kernel32.GetFinalPathNameByHandleW -except (ImportError, AttributeError): - def convert_path(s): - return s -else: - def convert_path(s): - if isinstance(s, bytes): - s = s.decode("mbcs") - hdir = ctypes.windll.kernel32.\ - CreateFileW(s, 0x80, # FILE_READ_ATTRIBUTES - 1, # FILE_SHARE_READ - None, 3, # OPEN_EXISTING - 0x02000000, # FILE_FLAG_BACKUP_SEMANTICS - None) - if hdir == -1: - # Cannot open directory, give up - return s - buf = ctypes.create_unicode_buffer("", 32768) - res = ctypes.windll.kernel32.\ - GetFinalPathNameByHandleW(hdir, buf, len(buf), - 0) # VOLUME_NAME_DOS - ctypes.windll.kernel32.CloseHandle(hdir) - if res == 0: - # Conversion failed (e.g. network location) - return s - s = buf[:res] - # Ignore leading \\?\ - if s.startswith("\\\\?\\"): - s = s[4:] - if s.startswith("UNC"): - s = "\\" + s[3:] - return s - -prefix = os.path.join(sys.base_prefix,"tcl") -if not os.path.exists(prefix): - # devdir/externals/tcltk/lib - prefix = os.path.join(sys.base_prefix, "externals", "tcltk", "lib") - prefix = os.path.abspath(prefix) -# if this does not exist, no further search is needed -if os.path.exists(prefix): - prefix = convert_path(prefix) - if "TCL_LIBRARY" not in os.environ: - for name in os.listdir(prefix): - if name.startswith("tcl"): - tcldir = os.path.join(prefix,name) - if os.path.isdir(tcldir): - os.environ["TCL_LIBRARY"] = tcldir - # Compute TK_LIBRARY, knowing that it has the same version - # as Tcl - import _tkinter - ver = str(_tkinter.TCL_VERSION) - if "TK_LIBRARY" not in os.environ: - v = os.path.join(prefix, 'tk'+ver) - if os.path.exists(os.path.join(v, "tclIndex")): - os.environ['TK_LIBRARY'] = v - # We don't know the Tix version, so we must search the entire - # directory - if "TIX_LIBRARY" not in os.environ: - for name in os.listdir(prefix): - if name.startswith("tix"): - tixdir = os.path.join(prefix,name) - if os.path.isdir(tixdir): - os.environ["TIX_LIBRARY"] = tixdir diff --git a/Misc/NEWS b/Misc/NEWS index 891b61e..9c858db 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -58,6 +58,10 @@ Core and Builtins Library ------- +- Issue #20035: Replaced the ``tkinter._fix`` module used for setting up the + Tcl/Tk environment on Windows with a private function in the ``_tkinter`` + module that makes no permanent changes to the environment. + - Issue #24257: Fixed segmentation fault in sqlite3.Row constructor with faked cursor type. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 4da836e..41ad5f9 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -101,7 +101,65 @@ Copyright (C) 1994 Steen Lumholt. #ifdef MS_WINDOWS #include #define WAIT_FOR_STDIN + +static PyObject * +_get_tcl_lib_path() +{ + static PyObject *tcl_library_path = NULL; + static int already_checked = 0; + + if (already_checked == 0) { + PyObject *prefix; + struct stat stat_buf; + int stat_return_value; + + prefix = PyUnicode_FromWideChar(Py_GetPrefix(), -1); + if (prefix == NULL) { + return NULL; + } + + /* Check expected location for an installed Python first */ + tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION); + if (tcl_library_path == NULL) { + return NULL; + } + tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path); + if (tcl_library_path == NULL) { + return NULL; + } + stat_return_value = _Py_stat(tcl_library_path, &stat_buf); + if (stat_return_value == -2) { + return NULL; + } + if (stat_return_value == -1) { + /* install location doesn't exist, reset errno and see if + we're a repository build */ + errno = 0; +#ifdef Py_TCLTK_DIR + tcl_library_path = PyUnicode_FromString( + Py_TCLTK_DIR "\\lib\\tcl" TCL_VERSION); + if (tcl_library_path == NULL) { + return NULL; + } + stat_return_value = _Py_stat(tcl_library_path, &stat_buf); + if (stat_return_value == -2) { + return NULL; + } + if (stat_return_value == -1) { + /* tcltkDir for a repository build doesn't exist either, + reset errno and leave Tcl to its own devices */ + errno = 0; + tcl_library_path = NULL; + } +#else + tcl_library_path = NULL; #endif + } + already_checked = 1; + } + return tcl_library_path; +} +#endif /* MS_WINDOWS */ #ifdef WITH_THREAD @@ -681,6 +739,33 @@ Tkapp_New(const char *screenName, const char *className, PyMem_Free(args); } +#ifdef MS_WINDOWS + { + PyObject *str_path; + PyObject *utf8_path; + DWORD ret; + + ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0); + if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + str_path = _get_tcl_lib_path(); + if (str_path == NULL && PyErr_Occurred()) { + return NULL; + } + if (str_path != NULL) { + utf8_path = PyUnicode_AsUTF8String(str_path); + if (utf8_path == NULL) { + return NULL; + } + Tcl_SetVar(v->interp, + "tcl_library", + PyBytes_AsString(utf8_path), + TCL_GLOBAL_ONLY); + Py_DECREF(utf8_path); + } + } + } +#endif + if (Tcl_AppInit(v->interp) != TCL_OK) { PyObject *result = Tkinter_Error((PyObject *)v); #ifdef TKINTER_PROTECT_LOADTK @@ -3517,8 +3602,40 @@ PyInit__tkinter(void) uexe = PyUnicode_FromWideChar(Py_GetProgramName(), -1); if (uexe) { cexe = PyUnicode_EncodeFSDefault(uexe); - if (cexe) + if (cexe) { +#ifdef MS_WINDOWS + int set_var = 0; + PyObject *str_path; + wchar_t *wcs_path; + DWORD ret; + + ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0); + + if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + str_path = _get_tcl_lib_path(); + if (str_path == NULL && PyErr_Occurred()) { + return NULL; + } + if (str_path != NULL) { + wcs_path = PyUnicode_AsWideCharString(str_path, NULL); + if (wcs_path == NULL) { + return NULL; + } + SetEnvironmentVariableW(L"TCL_LIBRARY", wcs_path); + set_var = 1; + } + } + Tcl_FindExecutable(PyBytes_AsString(cexe)); + + if (set_var) { + SetEnvironmentVariableW(L"TCL_LIBRARY", NULL); + PyMem_Free(wcs_path); + } +#else + Tcl_FindExecutable(PyBytes_AsString(cexe)); +#endif /* MS_WINDOWS */ + } Py_XDECREF(cexe); Py_DECREF(uexe); } diff --git a/PCbuild/_tkinter.vcxproj b/PCbuild/_tkinter.vcxproj index e1408a1..f3185eb 100644 --- a/PCbuild/_tkinter.vcxproj +++ b/PCbuild/_tkinter.vcxproj @@ -63,6 +63,7 @@ $(tcltkDir)include;%(AdditionalIncludeDirectories) WITH_APPINIT;%(PreprocessorDefinitions) + Py_TCLTK_DIR="$(tcltkDir.TrimEnd('\').Replace('\', '\\'))";%(PreprocessorDefinitions) $(tcltkLib);%(AdditionalDependencies) diff --git a/Tools/buildbot/test-amd64.bat b/Tools/buildbot/test-amd64.bat index 044026f..9467e86 100644 --- a/Tools/buildbot/test-amd64.bat +++ b/Tools/buildbot/test-amd64.bat @@ -1,7 +1,5 @@ @rem Used by the buildbot "test" step. setlocal -rem The following line should be removed before #20035 is closed -set TCL_LIBRARY=%~dp0..\..\externals\tcltk64\lib\tcl8.6 call "%~dp0..\..\PCbuild\rt.bat" -d -q -x64 -uall -rwW -n --timeout=3600 %* diff --git a/Tools/buildbot/test.bat b/Tools/buildbot/test.bat index 427957b..995f012 100644 --- a/Tools/buildbot/test.bat +++ b/Tools/buildbot/test.bat @@ -1,7 +1,5 @@ @rem Used by the buildbot "test" step. setlocal -rem The following line should be removed before #20035 is closed -set TCL_LIBRARY=%~dp0..\..\externals\tcltk\lib\tcl8.6 call "%~dp0..\..\PCbuild\rt.bat" -d -q -uall -rwW -n --timeout=3600 %* -- cgit v0.12