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.
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 @@
 * 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 @@
 # 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 @@
 # 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 @@
 # 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 @@
 # 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 @@
 """
 
 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_LIBRARY>\..\tcl<TCL_VERSION>, 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 @@
 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 @@
 #ifdef MS_WINDOWS
 #include <conio.h>
 #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 @@
         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 @@
     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 @@
     <ClCompile>
       <AdditionalIncludeDirectories>$(tcltkDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WITH_APPINIT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(BuildForRelease)' != 'true'">Py_TCLTK_DIR="$(tcltkDir.TrimEnd('\').Replace('\', '\\'))";%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
       <AdditionalDependencies>$(tcltkLib);%(AdditionalDependencies)</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 %*