Skip to content

Commit b545a9f

Browse files
Patch _tkinter.c to look in base prefix (#421)
## Summary This PR attempts to adresss one of the [known quirks](https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html#tcl-tk-support-files) around Tcl/tk files. On Windows, CPython will look for the Tcl/tk files at `{base_prefix}/lib`. That works well for python-build-standalone, since we ship the files in that location. However, on Unix, CPython does _not_ check that location -- instead, for python-build-standalone, it just looks relative to the executable. This works for non-virtual environments, since CPython _will_ look in `{prefix}/lib`. But not for virtual environments. The patch applied here ports the Windows discovery logic to Unix, so that we always hook up the right paths. The advantage of this approach (vis-a-vis others that were considered) is that it doesn't rely on injecting an environment variable into the user's environment. For now, I've only patched Python 3.13. Unfortunately, I'll need a _slightly_ different patch for each Python minor. I've already authored them locally, but I want to hold off on wiring them all up until we're aligned on this general approach. In theory, I think this change could be upstreamed. ## Test Plan - Build Python locally. - `./python/install/bin/python -c "from tkinter import Tk; Tk()"` - `./python/install/bin/python -m venv .venv` - `.venv/bin/python -c "from tkinter import Tk; Tk()"`
1 parent b16301a commit b545a9f

File tree

6 files changed

+612
-0
lines changed

6 files changed

+612
-0
lines changed

cpython-unix/build-cpython.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,21 @@ else
179179
patch -p1 -i ${ROOT}/patch-ctypes-callproc-legacy.patch
180180
fi
181181

182+
# On Windows, CPython looks for the Tcl/Tk libraries relative to the base prefix,
183+
# which we want. But on Unix, it doesn't. This patch applies similar behavior on Unix,
184+
# thereby ensuring that the Tcl/Tk libraries are found in the correct location.
185+
if [ "${PYTHON_MAJMIN_VERSION}" = "3.13" ]; then
186+
patch -p1 -i ${ROOT}/patch-tkinter-3.13.patch
187+
elif [ "${PYTHON_MAJMIN_VERSION}" = "3.12" ]; then
188+
patch -p1 -i ${ROOT}/patch-tkinter-3.12.patch
189+
elif [ "${PYTHON_MAJMIN_VERSION}" = "3.11" ]; then
190+
patch -p1 -i ${ROOT}/patch-tkinter-3.11.patch
191+
elif [ "${PYTHON_MAJMIN_VERSION}" = "3.10" ]; then
192+
patch -p1 -i ${ROOT}/patch-tkinter-3.10.patch
193+
else
194+
patch -p1 -i ${ROOT}/patch-tkinter-3.9.patch
195+
fi
196+
182197
# Code that runs at ctypes module import time does not work with
183198
# non-dynamic binaries. Patch Python to work around this.
184199
# See https://bugs.python.org/issue37060.

cpython-unix/patch-tkinter-3.10.patch

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
2+
index 2a3e65b6c97..b17c5bfd6b1 100644
3+
--- a/Modules/_tkinter.c
4+
+++ b/Modules/_tkinter.c
5+
@@ -115,6 +115,7 @@ Copyright (C) 1994 Steen Lumholt.
6+
#ifdef MS_WINDOWS
7+
#include <conio.h>
8+
#define WAIT_FOR_STDIN
9+
+#endif
10+
11+
static PyObject *
12+
_get_tcl_lib_path()
13+
@@ -132,6 +133,7 @@ _get_tcl_lib_path()
14+
return NULL;
15+
}
16+
17+
+#ifdef MS_WINDOWS
18+
/* Check expected location for an installed Python first */
19+
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
20+
if (tcl_library_path == NULL) {
21+
@@ -169,11 +171,31 @@ _get_tcl_lib_path()
22+
tcl_library_path = NULL;
23+
#endif
24+
}
25+
+#else
26+
+ /* Check expected location for an installed Python first */
27+
+ tcl_library_path = PyUnicode_FromString("/lib/tcl" TCL_VERSION);
28+
+ if (tcl_library_path == NULL) {
29+
+ return NULL;
30+
+ }
31+
+ tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
32+
+ if (tcl_library_path == NULL) {
33+
+ return NULL;
34+
+ }
35+
+ stat_return_value = _Py_stat(tcl_library_path, &stat_buf);
36+
+ if (stat_return_value == -2) {
37+
+ return NULL;
38+
+ }
39+
+ if (stat_return_value == -1) {
40+
+ /* install location doesn't exist, reset errno and leave Tcl
41+
+ to its own devices */
42+
+ errno = 0;
43+
+ tcl_library_path = NULL;
44+
+ }
45+
+#endif
46+
already_checked = 1;
47+
}
48+
return tcl_library_path;
49+
}
50+
-#endif /* MS_WINDOWS */
51+
52+
/* The threading situation is complicated. Tcl is not thread-safe, except
53+
when configured with --enable-threads.
54+
@@ -822,6 +844,30 @@ Tkapp_New(const char *screenName, const char *className,
55+
56+
ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0);
57+
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
58+
+ str_path = _get_tcl_lib_path();
59+
+ if (str_path == NULL && PyErr_Occurred()) {
60+
+ return NULL;
61+
+ }
62+
+ if (str_path != NULL) {
63+
+ utf8_path = PyUnicode_AsUTF8String(str_path);
64+
+ if (utf8_path == NULL) {
65+
+ return NULL;
66+
+ }
67+
+ Tcl_SetVar(v->interp,
68+
+ "tcl_library",
69+
+ PyBytes_AS_STRING(utf8_path),
70+
+ TCL_GLOBAL_ONLY);
71+
+ Py_DECREF(utf8_path);
72+
+ }
73+
+ }
74+
+ }
75+
+#else
76+
+ {
77+
+ const char *env_val = getenv("TCL_LIBRARY");
78+
+ if (!env_val) {
79+
+ PyObject *str_path;
80+
+ PyObject *utf8_path;
81+
+
82+
str_path = _get_tcl_lib_path();
83+
if (str_path == NULL && PyErr_Occurred()) {
84+
return NULL;
85+
@@ -3628,7 +3674,27 @@ PyInit__tkinter(void)
86+
PyMem_Free(wcs_path);
87+
}
88+
#else
89+
+ int set_var = 0;
90+
+ PyObject *str_path;
91+
+ char *path;
92+
+
93+
+ if (!getenv("TCL_LIBRARY")) {
94+
+ str_path = _get_tcl_lib_path();
95+
+ if (str_path == NULL && PyErr_Occurred()) {
96+
+ Py_DECREF(m);
97+
+ return NULL;
98+
+ }
99+
+ if (str_path != NULL) {
100+
+ setenv("TCL_LIBRARY", path, 1);
101+
+ set_var = 1;
102+
+ }
103+
+ }
104+
+
105+
Tcl_FindExecutable(PyBytes_AS_STRING(cexe));
106+
+
107+
+ if (set_var) {
108+
+ unsetenv("TCL_LIBRARY");
109+
+ }
110+
#endif /* MS_WINDOWS */
111+
}
112+
Py_XDECREF(cexe);

cpython-unix/patch-tkinter-3.11.patch

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
2+
index 005036d3ff2..c130ed7b186 100644
3+
--- a/Modules/_tkinter.c
4+
+++ b/Modules/_tkinter.c
5+
@@ -28,9 +28,7 @@ Copyright (C) 1994 Steen Lumholt.
6+
7+
#include "Python.h"
8+
#include <ctype.h>
9+
-#ifdef MS_WINDOWS
10+
-# include "pycore_fileutils.h" // _Py_stat()
11+
-#endif
12+
+#include "pycore_fileutils.h" // _Py_stat()
13+
14+
#ifdef MS_WINDOWS
15+
#include <windows.h>
16+
@@ -123,6 +121,7 @@ Copyright (C) 1994 Steen Lumholt.
17+
#ifdef MS_WINDOWS
18+
#include <conio.h>
19+
#define WAIT_FOR_STDIN
20+
+#endif
21+
22+
static PyObject *
23+
_get_tcl_lib_path()
24+
@@ -140,6 +139,7 @@ _get_tcl_lib_path()
25+
return NULL;
26+
}
27+
28+
+#ifdef MS_WINDOWS
29+
/* Check expected location for an installed Python first */
30+
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
31+
if (tcl_library_path == NULL) {
32+
@@ -177,11 +177,31 @@ _get_tcl_lib_path()
33+
tcl_library_path = NULL;
34+
#endif
35+
}
36+
+#else
37+
+ /* Check expected location for an installed Python first */
38+
+ tcl_library_path = PyUnicode_FromString("/lib/tcl" TCL_VERSION);
39+
+ if (tcl_library_path == NULL) {
40+
+ return NULL;
41+
+ }
42+
+ tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
43+
+ if (tcl_library_path == NULL) {
44+
+ return NULL;
45+
+ }
46+
+ stat_return_value = _Py_stat(tcl_library_path, &stat_buf);
47+
+ if (stat_return_value == -2) {
48+
+ return NULL;
49+
+ }
50+
+ if (stat_return_value == -1) {
51+
+ /* install location doesn't exist, reset errno and leave Tcl
52+
+ to its own devices */
53+
+ errno = 0;
54+
+ tcl_library_path = NULL;
55+
+ }
56+
+#endif
57+
already_checked = 1;
58+
}
59+
return tcl_library_path;
60+
}
61+
-#endif /* MS_WINDOWS */
62+
63+
/* The threading situation is complicated. Tcl is not thread-safe, except
64+
when configured with --enable-threads.
65+
@@ -687,6 +707,30 @@ Tkapp_New(const char *screenName, const char *className,
66+
67+
ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0);
68+
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
69+
+ str_path = _get_tcl_lib_path();
70+
+ if (str_path == NULL && PyErr_Occurred()) {
71+
+ return NULL;
72+
+ }
73+
+ if (str_path != NULL) {
74+
+ utf8_path = PyUnicode_AsUTF8String(str_path);
75+
+ if (utf8_path == NULL) {
76+
+ return NULL;
77+
+ }
78+
+ Tcl_SetVar(v->interp,
79+
+ "tcl_library",
80+
+ PyBytes_AS_STRING(utf8_path),
81+
+ TCL_GLOBAL_ONLY);
82+
+ Py_DECREF(utf8_path);
83+
+ }
84+
+ }
85+
+ }
86+
+#else
87+
+ {
88+
+ const char *env_val = getenv("TCL_LIBRARY");
89+
+ if (!env_val) {
90+
+ PyObject *str_path;
91+
+ PyObject *utf8_path;
92+
+
93+
str_path = _get_tcl_lib_path();
94+
if (str_path == NULL && PyErr_Occurred()) {
95+
return NULL;
96+
@@ -3428,7 +3472,27 @@ PyInit__tkinter(void)
97+
PyMem_Free(wcs_path);
98+
}
99+
#else
100+
+ int set_var = 0;
101+
+ PyObject *str_path;
102+
+ char *path;
103+
+
104+
+ if (!getenv("TCL_LIBRARY")) {
105+
+ str_path = _get_tcl_lib_path();
106+
+ if (str_path == NULL && PyErr_Occurred()) {
107+
+ Py_DECREF(m);
108+
+ return NULL;
109+
+ }
110+
+ if (str_path != NULL) {
111+
+ setenv("TCL_LIBRARY", path, 1);
112+
+ set_var = 1;
113+
+ }
114+
+ }
115+
+
116+
Tcl_FindExecutable(PyBytes_AS_STRING(cexe));
117+
+
118+
+ if (set_var) {
119+
+ unsetenv("TCL_LIBRARY");
120+
+ }
121+
#endif /* MS_WINDOWS */
122+
}
123+
Py_XDECREF(cexe);

cpython-unix/patch-tkinter-3.12.patch

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
2+
index 6b5fcb8a365..99d44ccf1d8 100644
3+
--- a/Modules/_tkinter.c
4+
+++ b/Modules/_tkinter.c
5+
@@ -28,9 +28,7 @@ Copyright (C) 1994 Steen Lumholt.
6+
7+
#include "Python.h"
8+
#include <ctype.h>
9+
-#ifdef MS_WINDOWS
10+
-# include "pycore_fileutils.h" // _Py_stat()
11+
-#endif
12+
+#include "pycore_fileutils.h" // _Py_stat()
13+
14+
#include "pycore_long.h"
15+
16+
@@ -134,6 +132,7 @@ typedef int Tcl_Size;
17+
#ifdef MS_WINDOWS
18+
#include <conio.h>
19+
#define WAIT_FOR_STDIN
20+
+#endif
21+
22+
static PyObject *
23+
_get_tcl_lib_path(void)
24+
@@ -151,6 +150,7 @@ _get_tcl_lib_path(void)
25+
return NULL;
26+
}
27+
28+
+#ifdef MS_WINDOWS
29+
/* Check expected location for an installed Python first */
30+
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
31+
if (tcl_library_path == NULL) {
32+
@@ -188,11 +188,31 @@ _get_tcl_lib_path(void)
33+
tcl_library_path = NULL;
34+
#endif
35+
}
36+
+#else
37+
+ /* Check expected location for an installed Python first */
38+
+ tcl_library_path = PyUnicode_FromString("/lib/tcl" TCL_VERSION);
39+
+ if (tcl_library_path == NULL) {
40+
+ return NULL;
41+
+ }
42+
+ tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
43+
+ if (tcl_library_path == NULL) {
44+
+ return NULL;
45+
+ }
46+
+ stat_return_value = _Py_stat(tcl_library_path, &stat_buf);
47+
+ if (stat_return_value == -2) {
48+
+ return NULL;
49+
+ }
50+
+ if (stat_return_value == -1) {
51+
+ /* install location doesn't exist, reset errno and leave Tcl
52+
+ to its own devices */
53+
+ errno = 0;
54+
+ tcl_library_path = NULL;
55+
+ }
56+
+#endif
57+
already_checked = 1;
58+
}
59+
return tcl_library_path;
60+
}
61+
-#endif /* MS_WINDOWS */
62+
63+
/* The threading situation is complicated. Tcl is not thread-safe, except
64+
when configured with --enable-threads.
65+
@@ -713,6 +733,30 @@ Tkapp_New(const char *screenName, const char *className,
66+
67+
ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0);
68+
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
69+
+ str_path = _get_tcl_lib_path();
70+
+ if (str_path == NULL && PyErr_Occurred()) {
71+
+ return NULL;
72+
+ }
73+
+ if (str_path != NULL) {
74+
+ utf8_path = PyUnicode_AsUTF8String(str_path);
75+
+ if (utf8_path == NULL) {
76+
+ return NULL;
77+
+ }
78+
+ Tcl_SetVar(v->interp,
79+
+ "tcl_library",
80+
+ PyBytes_AS_STRING(utf8_path),
81+
+ TCL_GLOBAL_ONLY);
82+
+ Py_DECREF(utf8_path);
83+
+ }
84+
+ }
85+
+ }
86+
+#else
87+
+ {
88+
+ const char *env_val = getenv("TCL_LIBRARY");
89+
+ if (!env_val) {
90+
+ PyObject *str_path;
91+
+ PyObject *utf8_path;
92+
+
93+
str_path = _get_tcl_lib_path();
94+
if (str_path == NULL && PyErr_Occurred()) {
95+
return NULL;
96+
@@ -3542,7 +3586,27 @@ PyInit__tkinter(void)
97+
PyMem_Free(wcs_path);
98+
}
99+
#else
100+
+ int set_var = 0;
101+
+ PyObject *str_path;
102+
+ char *path;
103+
+
104+
+ if (!getenv("TCL_LIBRARY")) {
105+
+ str_path = _get_tcl_lib_path();
106+
+ if (str_path == NULL && PyErr_Occurred()) {
107+
+ Py_DECREF(m);
108+
+ return NULL;
109+
+ }
110+
+ if (str_path != NULL) {
111+
+ setenv("TCL_LIBRARY", path, 1);
112+
+ set_var = 1;
113+
+ }
114+
+ }
115+
+
116+
Tcl_FindExecutable(PyBytes_AS_STRING(cexe));
117+
+
118+
+ if (set_var) {
119+
+ unsetenv("TCL_LIBRARY");
120+
+ }
121+
#endif /* MS_WINDOWS */
122+
}
123+
Py_XDECREF(cexe);

0 commit comments

Comments
 (0)