[3.14] gh-124111: Update Windows build to use Tcl/Tk 9.0.4 (GH-150102) (GH-149477) (GH-151562) (GH-153901) (#150197)

hugovk <[email protected]> Mon, 03 Aug 2026 06:10:50 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/29a9a3d8ce46b74290cb855455d83900b910882f
commit: 29a9a3d8ce46b74290cb855455d83900b910882f
branch: 3.14
author: Zachary Ware <[email protected]>
committer: hugovk <[email protected]>
date: 2026-08-03T13:10:34+03:00
summary:

[3.14] gh-124111: Update Windows build to use Tcl/Tk 9.0.4 (GH-150102) (GH-149477) (GH-151562) (GH-153901) (#150197)

Co-authored-by: Steve Dower <[email protected]>
Co-authored-by: Jonathan J. Helmus <[email protected]>

files:
A Misc/NEWS.d/next/Windows/2026-06-04-18-53-18.gh-issue-150836.Wci7bZ.rst
A Misc/NEWS.d/next/Windows/2026-07-18-11-56-53.gh-issue-124111.m4OBX8.rst
M Lib/test/test_tcl.py
M Misc/externals.spdx.json
M Modules/_tkinter.c
M Modules/tkappinit.c
M Modules/tkinter.h
M PCbuild/get_externals.bat
M PCbuild/readme.txt
M PCbuild/tcltk.props
M Tools/msi/tcltk/tcltk_files.wxs
M Tools/msi/testrelease.bat

diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index 47450d3fd5976fa..70731d3222ced94 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -54,7 +54,11 @@ def test_eval_null_in_result(self):
 
     def test_eval_surrogates_in_result(self):
         tcl = self.interp
-        self.assertEqual(tcl.eval(r'set a "<\ud83d\udcbb>"'), '<\U0001f4bb>')
+        result = tcl.eval(r'set a "<\ud83d\udcbb>"')
+        if sys.platform == 'win32' and tcl_version >= (9, 0):
+            self.assertEqual('<\ud83d\udcbb>', result)
+        else:
+            self.assertEqual('<\U0001f4bb>', result)
 
     def testEvalException(self):
         tcl = self.interp
@@ -289,7 +293,11 @@ def test_evalfile_surrogates_in_result(self):
             set b "<\\ud83d\\udcbb>"
             """)
         tcl.evalfile(filename)
-        self.assertEqual(tcl.eval('set b'), '<\U0001f4bb>')
+        result = tcl.eval('set b')
+        if sys.platform == 'win32' and tcl_version >= (9, 0):
+            self.assertEqual('<\ud83d\udcbb>', result)
+        else:
+            self.assertEqual('<\U0001f4bb>', result)
 
     def testEvalFileException(self):
         tcl = self.interp
diff --git a/Misc/NEWS.d/next/Windows/2026-06-04-18-53-18.gh-issue-150836.Wci7bZ.rst b/Misc/NEWS.d/next/Windows/2026-06-04-18-53-18.gh-issue-150836.Wci7bZ.rst
new file mode 100644
index 000000000000000..6497b7927db7da3
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-06-04-18-53-18.gh-issue-150836.Wci7bZ.rst
@@ -0,0 +1 @@
+Make installed tkinter work with Tcl/Tk 9 builds that embed the Tk script library in the Tk DLL on Windows.
diff --git a/Misc/NEWS.d/next/Windows/2026-07-18-11-56-53.gh-issue-124111.m4OBX8.rst b/Misc/NEWS.d/next/Windows/2026-07-18-11-56-53.gh-issue-124111.m4OBX8.rst
new file mode 100644
index 000000000000000..802afc6f7a6fc48
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-07-18-11-56-53.gh-issue-124111.m4OBX8.rst
@@ -0,0 +1 @@
+Updated Windows builds to use Tcl/Tk 9.0.4.
diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json
index cfc57981092c2e7..8751760f57673a5 100644
--- a/Misc/externals.spdx.json
+++ b/Misc/externals.spdx.json
@@ -108,46 +108,46 @@
       "versionInfo": "3.50.4.0"
     },
     {
-      "SPDXID": "SPDXRef-PACKAGE-tcl-core",
+      "SPDXID": "SPDXRef-PACKAGE-tcl",
       "checksums": [
         {
           "algorithm": "SHA256",
-          "checksumValue": "4c23f0dd3efcbe6f3a22c503a68d147617bb30c4f5290f1eb3eaacf0b460440b"
+          "checksumValue": "3ac2acd65ddaaac0b2b8df321b558419d21da20bf67ab39149b8248c85f0d214"
         }
       ],
-      "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-core-8.6.15.0.tar.gz",
+      "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-9.0.4.0.tar.gz",
       "externalRefs": [
         {
           "referenceCategory": "SECURITY",
-          "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*",
+          "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:9.0.4.0:*:*:*:*:*:*:*",
           "referenceType": "cpe23Type"
         }
       ],
       "licenseConcluded": "NOASSERTION",
-      "name": "tcl-core",
+      "name": "tcl",
       "primaryPackagePurpose": "SOURCE",
-      "versionInfo": "8.6.15.0"
+      "versionInfo": "9.0.4.0"
     },
     {
       "SPDXID": "SPDXRef-PACKAGE-tk",
       "checksums": [
         {
           "algorithm": "SHA256",
-          "checksumValue": "0ae56d39bca92865f338529557a1e56d110594184b6dc5a91339c5675751e264"
+          "checksumValue": "66048966cfa88989333ff1632f454e10e9248516bb45efda7b4d2ae5a61642cb"
         }
       ],
-      "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-8.6.15.0.tar.gz",
+      "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-9.0.4.1.tar.gz",
       "externalRefs": [
         {
           "referenceCategory": "SECURITY",
-          "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*",
+          "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:9.0.4.1:*:*:*:*:*:*:*",
           "referenceType": "cpe23Type"
         }
       ],
       "licenseConcluded": "NOASSERTION",
       "name": "tk",
       "primaryPackagePurpose": "SOURCE",
-      "versionInfo": "8.6.15.0"
+      "versionInfo": "9.0.4.1"
     },
     {
       "SPDXID": "SPDXRef-PACKAGE-xz",
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 3b373cdc366c170..22126b67d8f4a0b 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -54,6 +54,10 @@ Copyright (C) 1994 Steen Lumholt.
 #  include <tk.h>
 #endif
 
+#if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9
+#  include <tkPlatDecls.h>
+#endif
+
 #include "tkinter.h"
 
 #if TK_HEX_VERSION < 0x0805020c
@@ -199,6 +203,57 @@ _get_tcl_lib_path(void)
 }
 #endif /* MS_WINDOWS */
 
+#if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9
+static void
+mount_tk_dll_zip(void)
+{
+    HINSTANCE tk_module = Tk_GetHINSTANCE();
+    wchar_t *tk_path = NULL;
+    DWORD path_len = 0;
+    for (DWORD buffer_len = 256;
+         tk_path == NULL && buffer_len < (1024 * 1024);
+         buffer_len *= 2)
+    {
+        tk_path = (wchar_t *)PyMem_RawMalloc(
+            buffer_len * sizeof(*tk_path));
+        if (tk_path != NULL) {
+            path_len = GetModuleFileNameW(tk_module, tk_path, buffer_len);
+            if (path_len == buffer_len) {
+                PyMem_RawFree(tk_path);
+                tk_path = NULL;
+            }
+        }
+    }
+
+    if (tk_path == NULL || path_len == 0) {
+        PyMem_RawFree(tk_path);
+        return;
+    }
+
+    Tcl_DString utf8_path;
+
+    Tcl_DStringInit(&utf8_path);
+    Tcl_WCharToUtfDString(tk_path, path_len, &utf8_path);
+    /* Failure is harmless if the DLL has no embedded ZIP or if another
+       interpreter has already mounted it. */
+    (void) TclZipfs_Mount(NULL, Tcl_DStringValue(&utf8_path),
+                          "//zipfs:/lib/tk", NULL);
+    Tcl_DStringFree(&utf8_path);
+    PyMem_RawFree(tk_path);
+}
+#endif
+
+int
+Tkinter_TkInit(Tcl_Interp *interp)
+{
+#if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9
+    /* Tcl/Tk 9 may embed the tk_library in the Tk DLL which tcl_findLibrary
+       does not search. Mount the DLL using Zipfs if possible.  */
+    mount_tk_dll_zip();
+#endif
+    return Tk_Init(interp);
+}
+
 /* The threading situation is complicated.  Tcl is not thread-safe, except
    when configured with --enable-threads.
 
@@ -569,7 +624,7 @@ Tcl_AppInit(Tcl_Interp *interp)
         return TCL_OK;
     }
 
-    if (Tk_Init(interp) == TCL_ERROR) {
+    if (Tkinter_TkInit(interp) == TCL_ERROR) {
         PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp));
         return TCL_ERROR;
     }
@@ -3013,7 +3068,7 @@ _tkinter_tkapp_loadtk_impl(TkappObject *self)
         return NULL;
     }
     if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0)     {
-        if (Tk_Init(interp)             == TCL_ERROR) {
+        if (Tkinter_TkInit(interp)      == TCL_ERROR) {
             Tkinter_Error(self);
             return NULL;
         }
diff --git a/Modules/tkappinit.c b/Modules/tkappinit.c
index 4c4081e43a8e3dd..1075ccf24d44877 100644
--- a/Modules/tkappinit.c
+++ b/Modules/tkappinit.c
@@ -37,7 +37,7 @@ Tcl_AppInit(Tcl_Interp *interp)
         return TCL_OK;
     }
 
-    if (Tk_Init(interp) == TCL_ERROR) {
+    if (Tkinter_TkInit(interp) == TCL_ERROR) {
         return TCL_ERROR;
     }
 
diff --git a/Modules/tkinter.h b/Modules/tkinter.h
index 40281c217603318..b73e99b28a40212 100644
--- a/Modules/tkinter.h
+++ b/Modules/tkinter.h
@@ -16,4 +16,6 @@
                         (TK_RELEASE_LEVEL << 8) | \
                         (TK_RELEASE_SERIAL << 0))
 
+int Tkinter_TkInit(Tcl_Interp *interp);
+
 #endif /* !TKINTER_H */
diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat
index 4ce04d46ce2207b..3afa12672202327 100644
--- a/PCbuild/get_externals.bat
+++ b/PCbuild/get_externals.bat
@@ -57,8 +57,8 @@ if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries%  libffi-3.4.4
 if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries%     openssl-3.5.7
 set libraries=%libraries%                                       mpdecimal-4.0.0
 set libraries=%libraries%                                       sqlite-3.50.4.0
-if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.15.0
-if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.15.0
+if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-9.0.4.0
+if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-9.0.4.1
 set libraries=%libraries%                                       xz-5.2.5
 set libraries=%libraries%                                       zlib-ng-2.2.4
 set libraries=%libraries%                                       zstd-1.5.7
@@ -80,7 +80,7 @@ echo.Fetching external binaries...
 set binaries=
 if NOT "%IncludeLibffi%"=="false"  set binaries=%binaries% libffi-3.4.4
 if NOT "%IncludeSSL%"=="false"     set binaries=%binaries% openssl-bin-3.5.7
-if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.15.0
+if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-9.0.4.0
 if NOT "%IncludeSSLSrc%"=="false"  set binaries=%binaries% nasm-2.11.06
 if NOT "%IncludeLLVM%"=="false"    set binaries=%binaries% llvm-19.1.7.0
 
diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt
index 13ab06a3cf73a67..84f81d92ef95d0b 100644
--- a/PCbuild/readme.txt
+++ b/PCbuild/readme.txt
@@ -234,7 +234,7 @@ _sqlite3
     Homepage:
         https://www.sqlite.org/
 _tkinter
-    Wraps version 8.6.15 of the Tk windowing system, which is downloaded
+    Wraps version 9.0.4 of the Tk windowing system, which is downloaded
     from our binaries repository at
     https://github.com/python/cpython-bin-deps.
 
diff --git a/PCbuild/tcltk.props b/PCbuild/tcltk.props
index d26b36ba98e493d..1ab8e96122aa2a9 100644
--- a/PCbuild/tcltk.props
+++ b/PCbuild/tcltk.props
@@ -2,7 +2,7 @@
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Import Project="pyproject.props" Condition="$(__PyProject_Props_Imported) != 'true'" />
   <PropertyGroup>
-    <TclVersion Condition="$(TclVersion) == ''">8.6.15.0</TclVersion>
+    <TclVersion Condition="$(TclVersion) == ''">9.0.4.0</TclVersion>
     <TkVersion Condition="$(TkVersion) == ''">$(TclVersion)</TkVersion>
     <TclMajorVersion>$([System.Version]::Parse($(TclVersion)).Major)</TclMajorVersion>
     <TclMinorVersion>$([System.Version]::Parse($(TclVersion)).Minor)</TclMinorVersion>
@@ -12,7 +12,9 @@
     <TkMinorVersion>$([System.Version]::Parse($(TkVersion)).Minor)</TkMinorVersion>
     <TkPatchLevel>$([System.Version]::Parse($(TkVersion)).Build)</TkPatchLevel>
     <TkRevision>$([System.Version]::Parse($(TkVersion)).Revision)</TkRevision>
-    <tclDir Condition="$(tclDir) == ''">$(ExternalsDir)tcl-core-$(TclVersion)\</tclDir>
+    <!-- Back compat hack.  If you're here wondering about -core vs not, do us both a favor and just override the whole tclDir variable -->
+    <tclDir Condition="$(tclDir) == '' and $(TclMajorVersion) == '8'">$(ExternalsDir)tcl-core-$(TclVersion)\</tclDir>
+    <tclDir Condition="$(tclDir) == ''">$(ExternalsDir)tcl-$(TclVersion)\</tclDir>
     <tkDir Condition="$(tkDir) == ''">$(ExternalsDir)tk-$(TkVersion)\</tkDir>
     <tcltkDir Condition="$(tcltkDir) == ''">$(ExternalsDir)tcltk-$(TclVersion)\$(ArchName)\</tcltkDir>
     <tclWin32Exe Condition="$(Platform) == 'Win32'">$(tcltkDir)\bin\tclsh$(TclMajorVersion)$(TclMinorVersion)t.exe</tclWin32Exe>
diff --git a/Tools/msi/tcltk/tcltk_files.wxs b/Tools/msi/tcltk/tcltk_files.wxs
index 5dad7c98d4f048a..7c7784741d9178d 100644
--- a/Tools/msi/tcltk/tcltk_files.wxs
+++ b/Tools/msi/tcltk/tcltk_files.wxs
@@ -10,11 +10,14 @@
             <Component Id="_tkinter.lib" Directory="libs" Guid="*">
                 <File Name="_tkinter.lib" KeyPath="yes" />
             </Component>
-            <Component Id="tcl86t.dll" Directory="DLLs" Guid="*">
-                <File Name="tcl86t.dll" KeyPath="yes" />
+            <Component Id="tcl90.dll" Directory="DLLs" Guid="*">
+                <File Name="tcl90.dll" KeyPath="yes" />
             </Component>
-            <Component Id="tk86t.dll" Directory="DLLs" Guid="*">
-                <File Name="tk86t.dll" KeyPath="yes" />
+            <Component Id="tcl9tk90.dll" Directory="DLLs" Guid="*">
+                <File Name="tcl9tk90.dll" KeyPath="yes" />
+            </Component>
+            <Component Id="libtommath.dll" Directory="DLLs" Guid="*">
+                <File Name="libtommath.dll" KeyPath="yes" />
             </Component>
             <Component Id="zlib1.dll" Directory="DLLs" Guid="*">
                 <File Name="zlib1.dll" KeyPath="yes" />
diff --git a/Tools/msi/testrelease.bat b/Tools/msi/testrelease.bat
index 02bcca943cf79b4..db98f690151196c 100644
--- a/Tools/msi/testrelease.bat
+++ b/Tools/msi/testrelease.bat
@@ -88,9 +88,7 @@ exit /B 0
 )
 @if not errorlevel 1 (
     @echo Testing Tcl/tk
-    @set TCL_LIBRARY=%~2\Python\tcl\tcl8.6
     "%~2\Python\python.exe" -m test -uall -v test_ttk_guionly test_tk test_idle > "%~2\tcltk.txt" 2>&1
-    @set TCL_LIBRARY=
 )
 
 @set EXITCODE=%ERRORLEVEL%

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]