From b9aede0514db9113b503a33d953d83dc1be04136 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Thu, 17 Jul 2025 15:02:09 +0100 Subject: [PATCH 1/6] Add support for tkinter --- graalpython/lib-python/3/_tkinter/__init__.py | 55 ++ graalpython/lib-python/3/_tkinter/app.py | 591 ++++++++++++++++++ graalpython/lib-python/3/_tkinter/tclobj.py | 233 +++++++ .../lib-python/3/_tkinter/tklib_build.py | 251 ++++++++ 4 files changed, 1130 insertions(+) create mode 100644 graalpython/lib-python/3/_tkinter/__init__.py create mode 100644 graalpython/lib-python/3/_tkinter/app.py create mode 100644 graalpython/lib-python/3/_tkinter/tclobj.py create mode 100644 graalpython/lib-python/3/_tkinter/tklib_build.py diff --git a/graalpython/lib-python/3/_tkinter/__init__.py b/graalpython/lib-python/3/_tkinter/__init__.py new file mode 100644 index 0000000000..44508ceb40 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/__init__.py @@ -0,0 +1,55 @@ +# _tkinter package -- low-level interface to libtk and libtcl. +# +# This is an internal module, applications should "import tkinter" instead. +# +# This version is based PyPy which itself is based on cffi, and is a translation of _tkinter.c +# from CPython, version 2.7.4. + +import sys + +class TclError(Exception): + pass + +from .tklib_cffi import ffi as tkffi, lib as tklib + +from .app import TkApp +from .tclobj import TclObject as Tcl_Obj +from .app import FromTclString, ToTCLString + +TK_VERSION = FromTclString(tkffi.string(tklib.get_tk_version())) +TCL_VERSION = FromTclString(tkffi.string(tklib.get_tcl_version())) + +READABLE = tklib.TCL_READABLE +WRITABLE = tklib.TCL_WRITABLE +EXCEPTION = tklib.TCL_EXCEPTION +DONT_WAIT = tklib.TCL_DONT_WAIT + +def create(screenName=None, baseName=None, className=None, + interactive=False, wantobjects=False, wantTk=True, + sync=False, use=None): + return TkApp(screenName, baseName, className, + interactive, wantobjects, wantTk, sync, use) + +def dooneevent(flags=0): + return tklib.Tcl_DoOneEvent(flags) + + +def _flatten(item): + def _flatten1(output, item, depth): + if depth > 1000: + raise ValueError("nesting too deep in _flatten") + if not isinstance(item, (list, tuple)): + raise TypeError("argument must be sequence") + # copy items to output tuple + for o in item: + if isinstance(o, (list, tuple)): + _flatten1(output, o, depth + 1) + elif o is not None: + output.append(o) + + result = [] + _flatten1(result, item, 0) + return tuple(result) + +# Encoding is not specified explicitly, but "must be passed argv[0]" sounds like a simple conversion to raw bytes. +tklib.Tcl_FindExecutable(ToTCLString(sys.executable)) \ No newline at end of file diff --git a/graalpython/lib-python/3/_tkinter/app.py b/graalpython/lib-python/3/_tkinter/app.py new file mode 100644 index 0000000000..58dcfaa035 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/app.py @@ -0,0 +1,591 @@ +from .tklib_cffi import ffi as tkffi, lib as tklib +from . import TclError +from .tclobj import (TclObject, FromObj, FromTclString, ToTCLString, AsObj, TypeCache, + FromBignumObj, FromWideIntObj) + +import contextlib +import sys +import threading +import time + +import threading + +class _DummyLock(object): + "A lock-like object that does not do anything" + def acquire(self): + pass + def release(self): + pass + def __enter__(self): + pass + def __exit__(self, *exc): + pass + + +def varname_converter(input): + # Explicit handling of NUL character in strings a bytes here because tests require it. + if isinstance(input, bytes) and b'\0' in input: + raise ValueError("NUL character in string") + if isinstance(input, str) and '\0' in input: + raise ValueError("NUL character in string") + + if isinstance(input, TclObject): + return input.string + + return ToTCLString(input) + + +def Tcl_AppInit(app): + # For portable builds, try to load a local version of the libraries + from os.path import join, dirname, exists, sep + if sys.platform == 'win32': + lib_path = join(dirname(dirname(dirname(__file__))), 'tcl') + tcl_path = join(lib_path, 'tcl8.6') + tk_path = join(lib_path, 'tk8.6') + tcl_path = tcl_path.replace(sep, '/') + tk_path = tk_path.replace(sep, '/') + else: + lib_path = join(dirname(dirname(dirname(__file__))), 'lib') + tcl_path = join(lib_path, 'tcl') + tk_path = join(lib_path, 'tk') + if exists(tcl_path): + tklib.Tcl_Eval(app.interp, ToTCLString('set tcl_library "{0}"'.format(tcl_path))) + if exists(tk_path): + tklib.Tcl_Eval(app.interp, ToTCLString('set tk_library "{0}"'.format(tk_path))) + + if tklib.Tcl_Init(app.interp) == tklib.TCL_ERROR: + app.raiseTclError() + skip_tk_init = tklib.Tcl_GetVar( + app.interp, b"_tkinter_skip_tk_init", tklib.TCL_GLOBAL_ONLY) + if skip_tk_init and FromTclString(tkffi.string(skip_tk_init)) == "1": + return + + if tklib.Tk_Init(app.interp) == tklib.TCL_ERROR: + app.raiseTclError() + +class _CommandData(object): + def __new__(cls, app, name, func): + self = object.__new__(cls) + self.app = app + self.name = name + self.func = func + handle = tkffi.new_handle(self) + app._commands[name] = handle # To keep the command alive + return tkffi.cast("ClientData", handle) + + @tkffi.callback("Tcl_CmdProc") + def PythonCmd(clientData, interp, argc, argv): + self = tkffi.from_handle(clientData) + assert self.app.interp == interp + with self.app._tcl_lock_released(): + try: + args = [FromTclString(tkffi.string(arg)) for arg in argv[1:argc]] + result = self.func(*args) + obj = AsObj(result) + tklib.Tcl_SetObjResult(interp, obj) + except: + self.app.errorInCmd = True + self.app.exc_info = sys.exc_info() + return tklib.TCL_ERROR + else: + return tklib.TCL_OK + + @tkffi.callback("Tcl_CmdDeleteProc") + def PythonCmdDelete(clientData): + self = tkffi.from_handle(clientData) + app = self.app + del app._commands[self.name] + return + + +class TkApp(object): + _busywaitinterval = 0.02 # 20ms. + + def __new__(cls, screenName, baseName, className, + interactive, wantobjects, wantTk, sync, use): + + if not wantobjects: + raise NotImplementedError("wantobjects=True only") + self = object.__new__(cls) + self.interp = tklib.Tcl_CreateInterp() + self._wantobjects = wantobjects + # "threaded" is an optionally present member of "tcl_platform" when TCL was compiled with threading. + # Tcl_GetVar2Ex should return NULL when "threaded" is not present, so casting to a bool here is doing an implicit NULL-check. + self.threaded = bool(tklib.Tcl_GetVar2Ex( + self.interp, b"tcl_platform", b"threaded", + tklib.TCL_GLOBAL_ONLY)) + self.thread_id = tklib.Tcl_GetCurrentThread() + self.dispatching = False + self.quitMainLoop = False + self.errorInCmd = False + + if not self.threaded: + # TCL is not thread-safe, calls needs to be serialized. + self._tcl_lock = threading.RLock() + else: + self._tcl_lock = _DummyLock() + + self._typeCache = TypeCache() + self._commands = {} + + # Delete the 'exit' command, which can screw things up + tklib.Tcl_DeleteCommand(self.interp, b"exit") + + if screenName is not None: + tklib.Tcl_SetVar2(self.interp, b"env", b"DISPLAY", screenName, + tklib.TCL_GLOBAL_ONLY) + + if interactive: + tklib.Tcl_SetVar(self.interp, b"tcl_interactive", b"1", + tklib.TCL_GLOBAL_ONLY) + else: + tklib.Tcl_SetVar(self.interp, b"tcl_interactive", b"0", + tklib.TCL_GLOBAL_ONLY) + + # This is used to get the application class for Tk 4.1 and up + argv0 = className.lower().encode('ascii') + tklib.Tcl_SetVar(self.interp, b"argv0", argv0, + tklib.TCL_GLOBAL_ONLY) + + if not wantTk: + tklib.Tcl_SetVar(self.interp, b"_tkinter_skip_tk_init", b"1", + tklib.TCL_GLOBAL_ONLY) + + # some initial arguments need to be in argv + if sync or use: + args = b"" + if sync: + args += b"-sync" + if use: + if sync: + args += b" " + args += b"-use " + use + + tklib.Tcl_SetVar(self.interp, b"argv", args, + tklib.TCL_GLOBAL_ONLY) + + Tcl_AppInit(self) + # EnableEventHook() + self._typeCache.add_extra_types(self) + return self + + def __del__(self): + tklib.Tcl_DeleteInterp(self.interp) + # DisableEventHook() + + def raiseTclError(self): + if self.errorInCmd: + self.errorInCmd = False + raise self.exc_info[0](self.exc_info[1]).with_traceback(self.exc_info[2]) + raise TclError(FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp)))) + + def wantobjects(self): + return self._wantobjects + + def _check_tcl_appartment(self): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise RuntimeError("Calling Tcl from different appartment") + + @contextlib.contextmanager + def _tcl_lock_released(self): + "Context manager to temporarily release the tcl lock." + self._tcl_lock.release() + yield + self._tcl_lock.acquire() + + def loadtk(self): + # We want to guard against calling Tk_Init() multiple times + err = tklib.Tcl_Eval(self.interp, b"info exists tk_version") + if err == tklib.TCL_ERROR: + self.raiseTclError() + tk_exists = tklib.Tcl_GetStringResult(self.interp) + if not tk_exists or FromTclString(tkffi.string(tk_exists)) != "1": + err = tklib.Tk_Init(self.interp) + if err == tklib.TCL_ERROR: + self.raiseTclError() + + def interpaddr(self): + return int(tkffi.cast('size_t', self.interp)) + + def _var_invoke(self, func, *args, **kwargs): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + # The current thread is not the interpreter thread. + # Marshal the call to the interpreter thread, then wait + # for completion. + raise NotImplementedError("Call from another thread") + return func(*args, **kwargs) + + def _getvar(self, name1, name2=None, global_only=False): + name1 = varname_converter(name1) + if not name2: + name2 = tkffi.NULL + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + # Name encoding not explicitly statet, assuming UTF-8 here due to other APIs. + res = tklib.Tcl_GetVar2Ex(self.interp, name1, name2, flags) + if not res: + self.raiseTclError() + assert self._wantobjects + return FromObj(self, res) + + def _setvar(self, name1, value, global_only=False): + name1 = varname_converter(name1) + # XXX Acquire tcl lock??? + newval = AsObj(value) + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + res = tklib.Tcl_SetVar2Ex(self.interp, name1, tkffi.NULL, + newval, flags) + if not res: + self.raiseTclError() + + def _unsetvar(self, name1, name2=None, global_only=False): + name1 = varname_converter(name1) + if not name2: + name2 = tkffi.NULL + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + res = tklib.Tcl_UnsetVar2(self.interp, name1, name2, flags) + if res == tklib.TCL_ERROR: + self.raiseTclError() + + def getvar(self, name1, name2=None): + return self._var_invoke(self._getvar, name1, name2) + + def globalgetvar(self, name1, name2=None): + return self._var_invoke(self._getvar, name1, name2, global_only=True) + + def setvar(self, name1, value): + return self._var_invoke(self._setvar, name1, value) + + def globalsetvar(self, name1, value): + return self._var_invoke(self._setvar, name1, value, global_only=True) + + def unsetvar(self, name1, name2=None): + return self._var_invoke(self._unsetvar, name1, name2) + + def globalunsetvar(self, name1, name2=None): + return self._var_invoke(self._unsetvar, name1, name2, global_only=True) + + # COMMANDS + + def createcommand(self, cmdName, func): + if not callable(func): + raise TypeError("command not callable") + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + clientData = _CommandData(self, cmdName, func) + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + with self._tcl_lock: + res = tklib.Tcl_CreateCommand( + self.interp, ToTCLString(cmdName), _CommandData.PythonCmd, + clientData, _CommandData.PythonCmdDelete) + if not res: + raise TclError(b"can't create Tcl command") + + def deletecommand(self, cmdName): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + with self._tcl_lock: + res = tklib.Tcl_DeleteCommand(self.interp, ToTCLString(cmdName)) + if res == -1: + raise TclError("can't delete Tcl command") + + def call(self, *args): + flags = tklib.TCL_EVAL_DIRECT | tklib.TCL_EVAL_GLOBAL + + # If args is a single tuple, replace with contents of tuple + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + # We cannot call the command directly. Instead, we must + # marshal the parameters to the interpreter thread. + raise NotImplementedError("Call from another thread") + + # Allocate new array of object pointers. + objects = tkffi.new("Tcl_Obj*[]", len(args)) + argc = len(args) + try: + for i, arg in enumerate(args): + if arg is None: + argc = i + break + obj = AsObj(arg) + tklib.Tcl_IncrRefCount(obj) + objects[i] = obj + + with self._tcl_lock: + res = tklib.Tcl_EvalObjv(self.interp, argc, objects, flags) + if res == tklib.TCL_ERROR: + self.raiseTclError() + else: + result = self._callResult() + finally: + for obj in objects: + if obj: + tklib.Tcl_DecrRefCount(obj) + return result + + def _callResult(self): + assert self._wantobjects + value = tklib.Tcl_GetObjResult(self.interp) + # Not sure whether the IncrRef is necessary, but something + # may overwrite the interpreter result while we are + # converting it. + tklib.Tcl_IncrRefCount(value) + res = FromObj(self, value) + tklib.Tcl_DecrRefCount(value) + return res + + def eval(self, script): + self._check_tcl_appartment() + with self._tcl_lock: + res = tklib.Tcl_Eval(self.interp, script) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp))) + + def evalfile(self, filename): + self._check_tcl_appartment() + with self._tcl_lock: + res = tklib.Tcl_EvalFile(self.interp, filename) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp))) + + def split(self, arg): + if isinstance(arg, TclObject): + objc = tkffi.new("int*") + objv = tkffi.new("Tcl_Obj***") + status = tklib.Tcl_ListObjGetElements(self.interp, arg._value, objc, objv) + if status == tklib.TCL_ERROR: + return FromObj(self, arg._value) + if objc == 0: + return '' + elif objc == 1: + return FromObj(self, objv[0][0]) + result = [] + for i in range(objc[0]): + result.append(FromObj(self, objv[0][i])) + return tuple(result) + elif isinstance(arg, tuple): + return self._splitObj(arg) + elif isinstance(arg, str): + arg = ToTCLString(arg) + return self._split(arg) + + def splitlist(self, arg): + if isinstance(arg, TclObject): + objc = tkffi.new("int*") + objv = tkffi.new("Tcl_Obj***") + status = tklib.Tcl_ListObjGetElements(self.interp, arg._value, objc, objv) + if status == tklib.TCL_ERROR: + self.raiseTclError() + result = [] + for i in range(objc[0]): + result.append(FromObj(self, objv[0][i])) + return tuple(result) + elif isinstance(arg, tuple): + return arg + elif isinstance(arg, str): + arg = ToTCLString(arg) + + argc = tkffi.new("int*") + argv = tkffi.new("char***") + res = tklib.Tcl_SplitList(self.interp, arg, argc, argv) + if res == tklib.TCL_ERROR: + self.raiseTclError() + + result = tuple(FromTclString(tkffi.string(argv[0][i])) + for i in range(argc[0])) + tklib.Tcl_Free(argv[0]) + return result + + def _splitObj(self, arg): + if isinstance(arg, tuple): + size = len(arg) + result = None + # Recursively invoke SplitObj for all tuple items. + # If this does not return a new object, no action is + # needed. + for i in range(size): + elem = arg[i] + newelem = self._splitObj(elem) + if result is None: + if newelem == elem: + continue + result = [None] * size + for k in range(i): + result[k] = arg[k] + result[i] = newelem + if result is not None: + return tuple(result) + elif isinstance(arg, str): + argc = tkffi.new("int*") + argv = tkffi.new("char***") + if isinstance(arg, str): + arg = ToTCLString(arg) + list_ = str(arg) + res = tklib.Tcl_SplitList(tkffi.NULL, list_, argc, argv) + if res != tklib.TCL_OK: + return arg + tklib.Tcl_Free(argv[0]) + if argc[0] > 1: + return self._split(list_) + return arg + + def _split(self, arg): + argc = tkffi.new("int*") + argv = tkffi.new("char***") + res = tklib.Tcl_SplitList(tkffi.NULL, arg, argc, argv) + if res == tklib.TCL_ERROR: + # Not a list. + # Could be a quoted string containing funnies, e.g. {"}. + # Return the string itself. + return arg + + # TODO: Is this method called from Python and Python str is expected, or are TCL strings expected? + try: + if argc[0] == 0: + return "" + elif argc[0] == 1: + return FromTclString(tkffi.string(argv[0][0])) + else: + return tuple(self._split(argv[0][i]) + for i in range(argc[0])) + finally: + tklib.Tcl_Free(argv[0]) + + def getboolean(self, s): + if isinstance(s, int): + return bool(s) + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + v = tkffi.new("int*") + res = tklib.Tcl_GetBoolean(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return bool(v[0]) + + def getint(self, s): + if isinstance(s, int): + return s + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + if tklib.HAVE_LIBTOMMATH or tklib.HAVE_WIDE_INT_TYPE: + value = tklib.Tcl_NewStringObj(s, -1) + if not value: + self.raiseTclError() + try: + if tklib.HAVE_LIBTOMMATH: + return FromBignumObj(self, value) + else: + return FromWideIntObj(self, value) + finally: + tklib.Tcl_DecrRefCount(value) + else: + v = tkffi.new("int*") + res = tklib.Tcl_GetInt(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def getdouble(self, s): + if isinstance(s, float): + return s + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + v = tkffi.new("double*") + res = tklib.Tcl_GetDouble(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprboolean(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("int*") + res = tklib.Tcl_ExprBoolean(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprlong(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("long*") + res = tklib.Tcl_ExprLong(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprdouble(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("double*") + res = tklib.Tcl_ExprDouble(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprstring(self, s): + if b'\x00' in s: + raise TypeError + res = tklib.Tcl_ExprString(self.interp, ToTCLString(s)) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return tkffi.string(tklib.Tcl_GetStringResult(self.interp)) + + def mainloop(self, threshold): + self._check_tcl_appartment() + self.dispatching = True + while (tklib.Tk_GetNumMainWindows() > threshold and + not self.quitMainLoop and not self.errorInCmd): + + if self.threaded: + result = tklib.Tcl_DoOneEvent(0) + else: + with self._tcl_lock: + result = tklib.Tcl_DoOneEvent(tklib.TCL_DONT_WAIT) + if result == 0: + time.sleep(self._busywaitinterval) + + if result < 0: + break + self.dispatching = False + self.quitMainLoop = False + if self.errorInCmd: + self.errorInCmd = False + raise self.exc_info[0](self.exc_info[1]).with_traceback(self.exc_info[2]) + + def quit(self): + self.quitMainLoop = True + + def _createbytearray(self, buf): + """Convert Python string or any buffer compatible object to Tcl + byte-array object. Use it to pass binary data (e.g. image's + data) to Tcl/Tk commands.""" + cdata = tkffi.new("char[]", buf) + res = tklib.Tcl_NewByteArrayObj(cdata, len(buf)) + if not res: + self.raiseTclError() + return TclObject(res) \ No newline at end of file diff --git a/graalpython/lib-python/3/_tkinter/tclobj.py b/graalpython/lib-python/3/_tkinter/tclobj.py new file mode 100644 index 0000000000..667204e661 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/tclobj.py @@ -0,0 +1,233 @@ +from .tklib_cffi import ffi as tkffi, lib as tklib +import binascii + +class TypeCache(object): + def __init__(self): + self.OldBooleanType = tklib.Tcl_GetObjType(b"boolean") + self.BooleanType = None + self.ByteArrayType = tklib.Tcl_GetObjType(b"bytearray") + self.DoubleType = tklib.Tcl_GetObjType(b"double") + self.IntType = tklib.Tcl_GetObjType(b"int") + self.WideIntType = tklib.Tcl_GetObjType(b"wideInt") + self.BigNumType = None + self.ListType = tklib.Tcl_GetObjType(b"list") + self.ProcBodyType = tklib.Tcl_GetObjType(b"procbody") + self.StringType = tklib.Tcl_GetObjType(b"string") + + def add_extra_types(self, app): + # Some types are not registered in Tcl. + result = app.call('expr', 'true') + typePtr = AsObj(result).typePtr + if FromTclString(tkffi.string(typePtr.name)) == "booleanString": + self.BooleanType = typePtr + + result = app.call('expr', '2**63') + typePtr = AsObj(result).typePtr + if FromTclString(tkffi.string(typePtr.name)) == "bignum": + self.BigNumType = typePtr + + +# Interprets a TCL string (untyped char array) as a Python str using UTF-8. +# This assumes that TCL encodes its return values as UTF-8, not UTF-16. +# TODO: Find out whether this assumption is correct. +def FromTclString(s: bytes) -> str: + try: + return s.decode("utf-8") + except UnicodeDecodeError: + # Tcl encodes null character as \xc0\x80 + return s.replace(b'\xc0\x80', b'\x00') \ + .decode('utf-8') + +# Encodes a Python str as UTF-8 (assuming TCL encodes its API strings as UTF-8 as well, not UTF-16). +# TODO: Find out whether this is correct. +def ToTCLString(s: str) -> bytes: + return s.encode("utf-8") \ + .replace(b"\x00", b"\xc0\x80") + + +# Only when tklib.HAVE_WIDE_INT_TYPE. +def FromWideIntObj(app, value): + wide = tkffi.new("Tcl_WideInt*") + if tklib.Tcl_GetWideIntFromObj(app.interp, value, wide) != tklib.TCL_OK: + app.raiseTclError() + return int(wide[0]) + +# Only when tklib.HAVE_LIBTOMMATH! +def FromBignumObj(app, value): + bigValue = tkffi.new("mp_int*") + if tklib.Tcl_GetBignumFromObj(app.interp, value, bigValue) != tklib.TCL_OK: + app.raiseTclError() + try: + numBytes = tklib.mp_unsigned_bin_size(bigValue) + buf = tkffi.new("unsigned char[]", numBytes) + bufSize_ptr = tkffi.new("unsigned long*", numBytes) + if tklib.mp_to_unsigned_bin_n( + bigValue, buf, bufSize_ptr) != tklib.MP_OKAY: + raise MemoryError + if bufSize_ptr[0] == 0: + return 0 + bytes = tkffi.buffer(buf)[0:bufSize_ptr[0]] + sign = -1 if bigValue.sign == tklib.MP_NEG else 1 + return int(sign * int(binascii.hexlify(bytes), 16)) + finally: + tklib.mp_clear(bigValue) + +def AsBignumObj(value): + sign = -1 if value < 0 else 1 + hexstr = '%x' % abs(value) + bigValue = tkffi.new("mp_int*") + tklib.mp_init(bigValue) + try: + if tklib.mp_read_radix(bigValue, hexstr, 16) != tklib.MP_OKAY: + raise MemoryError + bigValue.sign = tklib.MP_NEG if value < 0 else tklib.MP_ZPOS + return tklib.Tcl_NewBignumObj(bigValue) + finally: + tklib.mp_clear(bigValue) + + +def FromObj(app, value): + """Convert a TclObj pointer into a Python object.""" + typeCache = app._typeCache + if not value.typePtr: + buf = tkffi.buffer(value.bytes, value.length) + return FromTclString(buf[:]) + + if value.typePtr in (typeCache.BooleanType, typeCache.OldBooleanType): + value_ptr = tkffi.new("int*") + if tklib.Tcl_GetBooleanFromObj( + app.interp, value, value_ptr) == tklib.TCL_ERROR: + app.raiseTclError() + return bool(value_ptr[0]) + if value.typePtr == typeCache.ByteArrayType: + size = tkffi.new('int*') + data = tklib.Tcl_GetByteArrayFromObj(value, size) + return tkffi.buffer(data, size[0])[:] + if value.typePtr == typeCache.DoubleType: + return value.internalRep.doubleValue + if value.typePtr == typeCache.IntType: + return value.internalRep.longValue + if value.typePtr == typeCache.WideIntType: + return FromWideIntObj(app, value) + if value.typePtr == typeCache.BigNumType and tklib.HAVE_LIBTOMMATH: + return FromBignumObj(app, value) + if value.typePtr == typeCache.ListType: + size = tkffi.new('int*') + status = tklib.Tcl_ListObjLength(app.interp, value, size) + if status == tklib.TCL_ERROR: + app.raiseTclError() + result = [] + tcl_elem = tkffi.new("Tcl_Obj**") + for i in range(size[0]): + status = tklib.Tcl_ListObjIndex(app.interp, + value, i, tcl_elem) + if status == tklib.TCL_ERROR: + app.raiseTclError() + result.append(FromObj(app, tcl_elem[0])) + return tuple(result) + if value.typePtr == typeCache.ProcBodyType: + pass # fall through and return tcl object. + if value.typePtr == typeCache.StringType: + buf = tklib.Tcl_GetUnicode(value) + length = tklib.Tcl_GetCharLength(value) + buf = tkffi.buffer(tkffi.cast("char*", buf), length*2)[:] + return buf.decode('utf-16') + + return TclObject(value) + +def AsObj(value): + if isinstance(value, str): + # TCL uses UTF-16 internally (https://www.tcl.tk/man/tcl8.4/TclCmd/encoding.html) + # But this function takes UTF-8 (https://linux.die.net/man/3/tcl_newstringobj#:~:text=array%20of%20UTF%2D8%2Dencoded%20bytes) + return tklib.Tcl_NewStringObj(ToTCLString(value), len(value)) + if isinstance(value, bool): + return tklib.Tcl_NewBooleanObj(value) + if isinstance(value, int): + try: + return tklib.Tcl_NewLongObj(value) + except OverflowError: + # 64-bit windows + if tklib.HAVE_WIDE_INT_TYPE: + return tklib.Tcl_NewWideIntObj(value) + else: + import sys + t, v, tb = sys.exc_info() + raise t(v).with_traceback(tb) + if isinstance(value, int): + try: + tkffi.new("long[]", [value]) + except OverflowError: + pass + else: + return tklib.Tcl_NewLongObj(value) + if tklib.HAVE_WIDE_INT_TYPE: + try: + tkffi.new("Tcl_WideInt[]", [value]) + except OverflowError: + pass + else: + return tklib.Tcl_NewWideIntObj(value) + if tklib.HAVE_LIBTOMMATH: + return AsBignumObj(value) + + if isinstance(value, float): + return tklib.Tcl_NewDoubleObj(value) + if isinstance(value, tuple): + argv = tkffi.new("Tcl_Obj*[]", len(value)) + for i in range(len(value)): + argv[i] = AsObj(value[i]) + return tklib.Tcl_NewListObj(len(value), argv) + if isinstance(value, str): + # TODO: Remnant of Python2's unicode type. What happens when our string contains unicode characters? + # Should we encode it as UTF-8 or UTF-16? + raise NotImplementedError + encoded = value.encode('utf-16')[2:] + buf = tkffi.new("char[]", encoded) + inbuf = tkffi.cast("Tcl_UniChar*", buf) + return tklib.Tcl_NewUnicodeObj(inbuf, len(encoded)/2) + if isinstance(value, TclObject): + return value._value + + return AsObj(str(value)) + +class TclObject(object): + def __new__(cls, value): + self = object.__new__(cls) + tklib.Tcl_IncrRefCount(value) + self._value = value + self._string = None + return self + + def __del__(self): + tklib.Tcl_DecrRefCount(self._value) + + def __str__(self): + if self._string and isinstance(self._string, str): + return self._string + return FromTclString(tkffi.string(tklib.Tcl_GetString(self._value))) + + def __repr__(self): + return "<%s object at 0x%x>" % ( + self.typename, tkffi.cast("intptr_t", self._value)) + + def __eq__(self, other): + if not isinstance(other, TclObject): + return NotImplemented + return self._value == other._value + + @property + def typename(self): + return FromTclString(tkffi.string(self._value.typePtr.name)) + + @property + def string(self): + if self._string is None: + length = tkffi.new("int*") + s = tklib.Tcl_GetStringFromObj(self._value, length) + value = tkffi.buffer(s, length[0])[:] + try: + value.decode('ascii') + except UnicodeDecodeError: + value = value.decode('utf8') + self._string = value + return self._string \ No newline at end of file diff --git a/graalpython/lib-python/3/_tkinter/tklib_build.py b/graalpython/lib-python/3/_tkinter/tklib_build.py new file mode 100644 index 0000000000..59e09f01e5 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/tklib_build.py @@ -0,0 +1,251 @@ +# C bindings with libtcl and libtk. + +from cffi import FFI +import sys, os + +# XXX find a better way to detect paths +# XXX pick up CPPFLAGS and LDFLAGS and add to these paths? +if sys.platform.startswith("openbsd"): + incdirs = ['/usr/local/include/tcl8.5', '/usr/local/include/tk8.5', '/usr/X11R6/include'] + linklibs = ['tk85', 'tcl85'] + libdirs = ['/usr/local/lib', '/usr/X11R6/lib'] +elif sys.platform.startswith("freebsd"): + incdirs = ['/usr/local/include/tcl8.6', '/usr/local/include/tk8.6', '/usr/local/include/X11', '/usr/local/include'] + linklibs = ['tk86', 'tcl86'] + libdirs = ['/usr/local/lib'] +elif sys.platform == 'win32': + incdirs = [] + linklibs = ['tcl86t', 'tk86t'] + libdirs = [] +elif sys.platform == 'darwin': + # homebrew + homebrew = os.environ.get('HOMEBREW_PREFIX', '') + incdirs = [ + '/opt/homebrew/opt/tcl-tk@8.6/include', + '/opt/homebrew/opt/tcl-tk@8.6/include/tcl-tk', + ] + + linklibs = ['tcl8.6', 'tk8.6'] + libdirs = [] + if homebrew: + incdirs.append(homebrew + '/include') + libdirs.append(homebrew + '/opt/tcl-tk@8.6/lib') +else: + # On some Linux distributions, the tcl and tk libraries are + # stored in /usr/include, so we must check this case also + libdirs = [] + found = False + for _ver in ['', '8.6', '8.5']: + incdirs = ['/usr/include/tcl' + _ver] + linklibs = ['tcl' + _ver, 'tk' + _ver] + if os.path.isdir(incdirs[0]): + found = True + break + if not found: + for _ver in ['8.6', '8.5', '']: + incdirs = [] + linklibs = ['tcl' + _ver, 'tk' + _ver] + for lib in ['/usr/lib/lib', '/usr/lib64/lib']: + if os.path.isfile(''.join([lib, linklibs[1], '.so'])): + found = True + break + if found: + break + if not found: + sys.stderr.write("*** TCL libraries not found! Falling back...\n") + incdirs = [] + linklibs = ['tcl', 'tk'] + +config_ffi = FFI() +config_ffi.cdef(""" +#define TK_HEX_VERSION ... +#define HAVE_WIDE_INT_TYPE ... +""") +config_lib = config_ffi.verify(""" +#include +#define TK_HEX_VERSION ((TK_MAJOR_VERSION << 24) | \ + (TK_MINOR_VERSION << 16) | \ + (TK_RELEASE_LEVEL << 8) | \ + (TK_RELEASE_SERIAL << 0)) +#ifdef TCL_WIDE_INT_TYPE +#define HAVE_WIDE_INT_TYPE 1 +#else +#define HAVE_WIDE_INT_TYPE 0 +#endif +""", + include_dirs=incdirs, + libraries=linklibs, + library_dirs = libdirs + ) + +TK_HEX_VERSION = config_lib.TK_HEX_VERSION + +HAVE_LIBTOMMATH = int((0x08050208 <= TK_HEX_VERSION < 0x08060000) or + (0x08060200 <= TK_HEX_VERSION)) +HAVE_WIDE_INT_TYPE = config_lib.HAVE_WIDE_INT_TYPE + +tkffi = FFI() + +tkffi.cdef(""" +char *get_tk_version(); +char *get_tcl_version(); +#define HAVE_LIBTOMMATH ... +#define HAVE_WIDE_INT_TYPE ... + +#define TCL_READABLE ... +#define TCL_WRITABLE ... +#define TCL_EXCEPTION ... +#define TCL_ERROR ... +#define TCL_OK ... + +#define TCL_LEAVE_ERR_MSG ... +#define TCL_GLOBAL_ONLY ... +#define TCL_EVAL_DIRECT ... +#define TCL_EVAL_GLOBAL ... + +#define TCL_DONT_WAIT ... + +typedef unsigned short Tcl_UniChar; +typedef ... Tcl_Interp; +typedef ...* Tcl_ThreadId; +typedef ...* Tcl_Command; + +typedef struct Tcl_ObjType { + const char *name; + ...; +} Tcl_ObjType; +typedef struct Tcl_Obj { + char *bytes; + int length; + const Tcl_ObjType *typePtr; + union { /* The internal representation: */ + long longValue; /* - an long integer value. */ + double doubleValue; /* - a double-precision floating value. */ + struct { /* - internal rep as two pointers. */ + void *ptr1; + void *ptr2; + } twoPtrValue; + } internalRep; + ...; +} Tcl_Obj; + +Tcl_Interp *Tcl_CreateInterp(); +void Tcl_DeleteInterp(Tcl_Interp* interp); +int Tcl_Init(Tcl_Interp* interp); +int Tk_Init(Tcl_Interp* interp); + +void Tcl_Free(void* ptr); + +const char *Tcl_SetVar(Tcl_Interp* interp, const char* varName, const char* newValue, int flags); +const char *Tcl_SetVar2(Tcl_Interp* interp, const char* name1, const char* name2, const char* newValue, int flags); +const char *Tcl_GetVar(Tcl_Interp* interp, const char* varName, int flags); +Tcl_Obj *Tcl_SetVar2Ex(Tcl_Interp* interp, const char* name1, const char* name2, Tcl_Obj* newValuePtr, int flags); +Tcl_Obj *Tcl_GetVar2Ex(Tcl_Interp* interp, const char* name1, const char* name2, int flags); +int Tcl_UnsetVar2(Tcl_Interp* interp, const char* name1, const char* name2, int flags); +const Tcl_ObjType *Tcl_GetObjType(const char* typeName); + +Tcl_Obj *Tcl_NewStringObj(const char* bytes, int length); +Tcl_Obj *Tcl_NewUnicodeObj(const Tcl_UniChar* unicode, int numChars); +Tcl_Obj *Tcl_NewLongObj(long longValue); +Tcl_Obj *Tcl_NewBooleanObj(int boolValue); +Tcl_Obj *Tcl_NewDoubleObj(double doubleValue); + +void Tcl_IncrRefCount(Tcl_Obj* objPtr); +void Tcl_DecrRefCount(Tcl_Obj* objPtr); + +int Tcl_GetBoolean(Tcl_Interp* interp, const char* src, int* boolPtr); +int Tcl_GetInt(Tcl_Interp* interp, const char* src, int* intPtr); +int Tcl_GetDouble(Tcl_Interp* interp, const char* src, double* doublePtr); +int Tcl_GetBooleanFromObj(Tcl_Interp* interp, Tcl_Obj* objPtr, int* valuePtr); +char *Tcl_GetString(Tcl_Obj* objPtr); +char *Tcl_GetStringFromObj(Tcl_Obj* objPtr, int* lengthPtr); +unsigned char *Tcl_GetByteArrayFromObj(Tcl_Obj* objPtr, int* lengthPtr); +Tcl_Obj *Tcl_NewByteArrayObj(unsigned char *bytes, int length); + +int Tcl_ExprBoolean(Tcl_Interp* interp, const char *expr, int *booleanPtr); +int Tcl_ExprLong(Tcl_Interp* interp, const char *expr, long* longPtr); +int Tcl_ExprDouble(Tcl_Interp* interp, const char *expr, double* doublePtr); +int Tcl_ExprString(Tcl_Interp* interp, const char *expr); + +Tcl_UniChar *Tcl_GetUnicode(Tcl_Obj* objPtr); +int Tcl_GetCharLength(Tcl_Obj* objPtr); + +Tcl_Obj *Tcl_NewListObj(int objc, Tcl_Obj* const objv[]); +int Tcl_ListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, int *objcPtr, Tcl_Obj ***objvPtr); +int Tcl_ListObjLength(Tcl_Interp* interp, Tcl_Obj* listPtr, int* intPtr); +int Tcl_ListObjIndex(Tcl_Interp* interp, Tcl_Obj* listPtr, int index, Tcl_Obj** objPtrPtr); +int Tcl_SplitList(Tcl_Interp* interp, char* list, int* argcPtr, const char*** argvPtr); + +int Tcl_Eval(Tcl_Interp* interp, const char* script); +int Tcl_EvalFile(Tcl_Interp* interp, const char* filename); +int Tcl_EvalObjv(Tcl_Interp* interp, int objc, Tcl_Obj** objv, int flags); +Tcl_Obj *Tcl_GetObjResult(Tcl_Interp* interp); +const char *Tcl_GetStringResult(Tcl_Interp* interp); +void Tcl_SetObjResult(Tcl_Interp* interp, Tcl_Obj* objPtr); + +typedef void* ClientData; +typedef int Tcl_CmdProc( + ClientData clientData, + Tcl_Interp *interp, + int argc, + const char *argv[]); +typedef void Tcl_CmdDeleteProc( + ClientData clientData); +Tcl_Command Tcl_CreateCommand(Tcl_Interp* interp, const char* cmdName, Tcl_CmdProc proc, ClientData clientData, Tcl_CmdDeleteProc deleteProc); +int Tcl_DeleteCommand(Tcl_Interp* interp, const char* cmdName); + +Tcl_ThreadId Tcl_GetCurrentThread(); +int Tcl_DoOneEvent(int flags); + +int Tk_GetNumMainWindows(); +void Tcl_FindExecutable(char *argv0); +""") + +if HAVE_WIDE_INT_TYPE: + tkffi.cdef(""" +typedef int... Tcl_WideInt; + +int Tcl_GetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_WideInt *value); +Tcl_Obj *Tcl_NewWideIntObj(Tcl_WideInt value); +""") + +if HAVE_LIBTOMMATH: + tkffi.cdef(""" +#define MP_OKAY ... +#define MP_ZPOS ... +#define MP_NEG ... +typedef struct { + int sign; + ...; +} mp_int; + +int Tcl_GetBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, mp_int *value); +Tcl_Obj *Tcl_NewBignumObj(mp_int *value); + +int mp_unsigned_bin_size(mp_int *a); +int mp_to_unsigned_bin_n(mp_int * a, unsigned char *b, unsigned long *outlen); +int mp_read_radix(mp_int *a, const char *str, int radix); +int mp_init(mp_int *a); +void mp_clear(mp_int *a); +""") + +tkffi.set_source("_tkinter.tklib_cffi", """ +#define HAVE_LIBTOMMATH %(HAVE_LIBTOMMATH)s +#define HAVE_WIDE_INT_TYPE %(HAVE_WIDE_INT_TYPE)s +#include +#include + +#if HAVE_LIBTOMMATH +#include +#endif + +char *get_tk_version(void) { return TK_VERSION; } +char *get_tcl_version(void) { return TCL_VERSION; } +""" % globals(), + include_dirs=incdirs, + libraries=linklibs, + library_dirs = libdirs + ) + +if __name__ == "__main__": + tkffi.compile(os.path.join(os.path.dirname(sys.argv[0]), '..')) \ No newline at end of file From 031123e419d96f5927b5819a8fa626838fcc6b06 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Mon, 4 Aug 2025 16:11:49 +0100 Subject: [PATCH 2/6] add automatic setup helper for GraalPy Tkinter support on macOS - Added setup_tkinter() function to handle missing _tkinter module - Automatically installs pip dependencies and system requirements --- .../lib-python/3/@test_90040_tmp\303\203" | Bin 0 -> 63 bytes .../__pycache__/_cffi__x993bc6b9x15ee1dff.c | 356 ++++++++++++++++++ .../lib-python/3/_tkinter/tklib_build.py | 6 +- graalpython/lib-python/3/tkinter/__init__.py | 49 ++- 4 files changed, 406 insertions(+), 5 deletions(-) create mode 100644 "graalpython/lib-python/3/@test_90040_tmp\303\203" create mode 100644 graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c diff --git "a/graalpython/lib-python/3/@test_90040_tmp\303\203" "b/graalpython/lib-python/3/@test_90040_tmp\303\203" new file mode 100644 index 0000000000000000000000000000000000000000..4e277643aaac912cbe015e6651c3d54e1ae256cd GIT binary patch literal 63 zcmZ?wbhEHbWMW`uSj50^^X5&vl0`O!^UN|Qe|nhr9|jbEvM@3LfewfOsbyehkuYMI H!e9*mG!zz8 literal 0 HcmV?d00001 diff --git a/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c b/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c new file mode 100644 index 0000000000..4ec029ca4b --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c @@ -0,0 +1,356 @@ + +#include +#include + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_CheckExact +# undef PyCapsule_GetPointer +# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_NUM_EXPORTS 25 + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static int _cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + int was_alive = (_cffi_types != NULL); + (void)self; /* unused */ + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + if (_cffi_setup_custom(library) < 0) + return NULL; + return PyBool_FromLong(was_alive); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static int _cffi_convert_array_argument(CTypeDescrObject *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static void _cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +static int _cffi_init(void) +{ + PyObject *module, *c_api_object = NULL; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + goto failure; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + goto failure; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); + Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ + + + +#include +#define TK_HEX_VERSION ((TK_MAJOR_VERSION << 24) | (TK_MINOR_VERSION << 16) | (TK_RELEASE_LEVEL << 8) | (TK_RELEASE_SERIAL << 0)) +#ifdef TCL_WIDE_INT_TYPE +#define HAVE_WIDE_INT_TYPE 1 +#else +#define HAVE_WIDE_INT_TYPE 0 +#endif + + +static int _cffi_const_HAVE_WIDE_INT_TYPE(PyObject *lib) +{ + PyObject *o; + int res; + o = _cffi_from_c_int_const(HAVE_WIDE_INT_TYPE); + if (o == NULL) + return -1; + res = PyObject_SetAttrString(lib, "HAVE_WIDE_INT_TYPE", o); + Py_DECREF(o); + if (res < 0) + return -1; + return ((void)lib,0); +} + +static int _cffi_const_TK_HEX_VERSION(PyObject *lib) +{ + PyObject *o; + int res; + o = _cffi_from_c_int_const(TK_HEX_VERSION); + if (o == NULL) + return -1; + res = PyObject_SetAttrString(lib, "TK_HEX_VERSION", o); + Py_DECREF(o); + if (res < 0) + return -1; + return _cffi_const_HAVE_WIDE_INT_TYPE(lib); +} + +static int _cffi_setup_custom(PyObject *lib) +{ + return _cffi_const_TK_HEX_VERSION(lib); +} + +static PyMethodDef _cffi_methods[] = { + {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 + +static struct PyModuleDef _cffi_module_def = { + PyModuleDef_HEAD_INIT, + "_cffi__x993bc6b9x15ee1dff", + NULL, + -1, + _cffi_methods, + NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC +PyInit__cffi__x993bc6b9x15ee1dff(void) +{ + PyObject *lib; + lib = PyModule_Create(&_cffi_module_def); + if (lib == NULL) + return NULL; + if (((void)lib,0) < 0 || _cffi_init() < 0) { + Py_DECREF(lib); + return NULL; + } + return lib; +} + +#else + +PyMODINIT_FUNC +init_cffi__x993bc6b9x15ee1dff(void) +{ + PyObject *lib; + lib = Py_InitModule("_cffi__x993bc6b9x15ee1dff", _cffi_methods); + if (lib == NULL) + return; + if (((void)lib,0) < 0 || _cffi_init() < 0) + return; + return; +} + +#endif diff --git a/graalpython/lib-python/3/_tkinter/tklib_build.py b/graalpython/lib-python/3/_tkinter/tklib_build.py index 59e09f01e5..c079e3c20c 100644 --- a/graalpython/lib-python/3/_tkinter/tklib_build.py +++ b/graalpython/lib-python/3/_tkinter/tklib_build.py @@ -21,15 +21,15 @@ # homebrew homebrew = os.environ.get('HOMEBREW_PREFIX', '') incdirs = [ - '/opt/homebrew/opt/tcl-tk@8.6/include', - '/opt/homebrew/opt/tcl-tk@8.6/include/tcl-tk', + '/opt/homebrew/opt/tcl-tk@8/include', + '/opt/homebrew/opt/tcl-tk@8/include/tcl-tk', ] linklibs = ['tcl8.6', 'tk8.6'] libdirs = [] if homebrew: incdirs.append(homebrew + '/include') - libdirs.append(homebrew + '/opt/tcl-tk@8.6/lib') + libdirs.append(homebrew + '/opt/tcl-tk@8/lib') else: # On some Linux distributions, the tcl and tk libraries are # stored in /usr/include, so we must check this case also diff --git a/graalpython/lib-python/3/tkinter/__init__.py b/graalpython/lib-python/3/tkinter/__init__.py index ac70d965f3..a5e7da0680 100644 --- a/graalpython/lib-python/3/tkinter/__init__.py +++ b/graalpython/lib-python/3/tkinter/__init__.py @@ -34,9 +34,54 @@ import enum import sys import types +import traceback + +def _setup_tkinter(): + import os + import subprocess + + print("GraalPy requires additional setup to enable Tkinter support on macOS.\n") + + print("Ensure pip and required packages are installed:") + print(" mx python -m ensurepip") + print(" mx python -m pip install cffi setuptools\n") + + print("Install system dependencies:") + print(" brew install tcl-tk@8") + + if sys.stdin.isatty(): + resp = input("Would you like to run pip setup and build now? [Y/n]: ").strip().lower() + if resp in ("", "y", "yes"): + try: + subprocess.check_call(["mx", "python", "-m", "ensurepip"]) + subprocess.check_call(["mx", "python", "-m", "pip", "install", "cffi", "setuptools"]) + subprocess.check_call(["brew", "install", "tcl-tk@8"]) + current_dir = os.path.dirname(__file__) + tklib_build_path = os.path.abspath(os.path.join( + current_dir, + "../../../../../darwin-aarch64/GRAALPY_JVM_STANDALONE/lib/python3.12/_tkinter/tklib_build.py" + )) + + subprocess.check_call(["mx", "python", tklib_build_path]) + except Exception as build_err: + raise build_err + else: + print("\n Run in an interactive terminal to auto-run pip setup and build.") + + + +try: + import _tkinter +except ModuleNotFoundError as e: + tb = traceback.format_exc() + missing_tklib = "_tkinter.tklib_cffi" in tb or "_tkinter" in tb + + if missing_tklib: + _setup_tkinter() + import _tkinter + else: + TclError = _tkinter.TclError -import _tkinter # If this fails your Python may not be configured for Tk -TclError = _tkinter.TclError from tkinter.constants import * import re From dc86ff0acb966009f8b3205727967d82beda2544 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Mon, 4 Aug 2025 16:14:41 +0100 Subject: [PATCH 3/6] add automatic setup helper for GraalPy Tkinter support on macOS - Added setup_tkinter() function to handle missing _tkinter module - Automatically installs pip dependencies and system requirements --- .../lib-python/3/@test_90040_tmp\303\203" | Bin 63 -> 0 bytes .../__pycache__/_cffi__x993bc6b9x15ee1dff.c | 356 ------------------ 2 files changed, 356 deletions(-) delete mode 100644 "graalpython/lib-python/3/@test_90040_tmp\303\203" delete mode 100644 graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c diff --git "a/graalpython/lib-python/3/@test_90040_tmp\303\203" "b/graalpython/lib-python/3/@test_90040_tmp\303\203" deleted file mode 100644 index 4e277643aaac912cbe015e6651c3d54e1ae256cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63 zcmZ?wbhEHbWMW`uSj50^^X5&vl0`O!^UN|Qe|nhr9|jbEvM@3LfewfOsbyehkuYMI H!e9*mG!zz8 diff --git a/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c b/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c deleted file mode 100644 index 4ec029ca4b..0000000000 --- a/graalpython/lib-python/3/_tkinter/__pycache__/_cffi__x993bc6b9x15ee1dff.c +++ /dev/null @@ -1,356 +0,0 @@ - -#include -#include - -/* this block of #ifs should be kept exactly identical between - c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py - and cffi/_cffi_include.h */ -#if defined(_MSC_VER) -# include /* for alloca() */ -# if _MSC_VER < 1600 /* MSVC < 2010 */ - typedef __int8 int8_t; - typedef __int16 int16_t; - typedef __int32 int32_t; - typedef __int64 int64_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; - typedef unsigned __int64 uint64_t; - typedef __int8 int_least8_t; - typedef __int16 int_least16_t; - typedef __int32 int_least32_t; - typedef __int64 int_least64_t; - typedef unsigned __int8 uint_least8_t; - typedef unsigned __int16 uint_least16_t; - typedef unsigned __int32 uint_least32_t; - typedef unsigned __int64 uint_least64_t; - typedef __int8 int_fast8_t; - typedef __int16 int_fast16_t; - typedef __int32 int_fast32_t; - typedef __int64 int_fast64_t; - typedef unsigned __int8 uint_fast8_t; - typedef unsigned __int16 uint_fast16_t; - typedef unsigned __int32 uint_fast32_t; - typedef unsigned __int64 uint_fast64_t; - typedef __int64 intmax_t; - typedef unsigned __int64 uintmax_t; -# else -# include -# endif -# if _MSC_VER < 1800 /* MSVC < 2013 */ -# ifndef __cplusplus - typedef unsigned char _Bool; -# endif -# endif -# define _cffi_float_complex_t _Fcomplex /* include for it */ -# define _cffi_double_complex_t _Dcomplex /* include for it */ -#else -# include -# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) -# include -# endif -# define _cffi_float_complex_t float _Complex -# define _cffi_double_complex_t double _Complex -#endif - -#if PY_MAJOR_VERSION < 3 -# undef PyCapsule_CheckExact -# undef PyCapsule_GetPointer -# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) -# define PyCapsule_GetPointer(capsule, name) \ - (PyCObject_AsVoidPtr(capsule)) -#endif - -#if PY_MAJOR_VERSION >= 3 -# define PyInt_FromLong PyLong_FromLong -#endif - -#define _cffi_from_c_double PyFloat_FromDouble -#define _cffi_from_c_float PyFloat_FromDouble -#define _cffi_from_c_long PyInt_FromLong -#define _cffi_from_c_ulong PyLong_FromUnsignedLong -#define _cffi_from_c_longlong PyLong_FromLongLong -#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong -#define _cffi_from_c__Bool PyBool_FromLong - -#define _cffi_to_c_double PyFloat_AsDouble -#define _cffi_to_c_float PyFloat_AsDouble - -#define _cffi_from_c_int_const(x) \ - (((x) > 0) ? \ - ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ - PyInt_FromLong((long)(x)) : \ - PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ - ((long long)(x) >= (long long)LONG_MIN) ? \ - PyInt_FromLong((long)(x)) : \ - PyLong_FromLongLong((long long)(x))) - -#define _cffi_from_c_int(x, type) \ - (((type)-1) > 0 ? /* unsigned */ \ - (sizeof(type) < sizeof(long) ? \ - PyInt_FromLong((long)x) : \ - sizeof(type) == sizeof(long) ? \ - PyLong_FromUnsignedLong((unsigned long)x) : \ - PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ - (sizeof(type) <= sizeof(long) ? \ - PyInt_FromLong((long)x) : \ - PyLong_FromLongLong((long long)x))) - -#define _cffi_to_c_int(o, type) \ - ((type)( \ - sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ - : (type)_cffi_to_c_i8(o)) : \ - sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ - : (type)_cffi_to_c_i16(o)) : \ - sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ - : (type)_cffi_to_c_i32(o)) : \ - sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ - : (type)_cffi_to_c_i64(o)) : \ - (Py_FatalError("unsupported size for type " #type), (type)0))) - -#define _cffi_to_c_i8 \ - ((int(*)(PyObject *))_cffi_exports[1]) -#define _cffi_to_c_u8 \ - ((int(*)(PyObject *))_cffi_exports[2]) -#define _cffi_to_c_i16 \ - ((int(*)(PyObject *))_cffi_exports[3]) -#define _cffi_to_c_u16 \ - ((int(*)(PyObject *))_cffi_exports[4]) -#define _cffi_to_c_i32 \ - ((int(*)(PyObject *))_cffi_exports[5]) -#define _cffi_to_c_u32 \ - ((unsigned int(*)(PyObject *))_cffi_exports[6]) -#define _cffi_to_c_i64 \ - ((long long(*)(PyObject *))_cffi_exports[7]) -#define _cffi_to_c_u64 \ - ((unsigned long long(*)(PyObject *))_cffi_exports[8]) -#define _cffi_to_c_char \ - ((int(*)(PyObject *))_cffi_exports[9]) -#define _cffi_from_c_pointer \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) -#define _cffi_to_c_pointer \ - ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) -#define _cffi_get_struct_layout \ - ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) -#define _cffi_restore_errno \ - ((void(*)(void))_cffi_exports[13]) -#define _cffi_save_errno \ - ((void(*)(void))_cffi_exports[14]) -#define _cffi_from_c_char \ - ((PyObject *(*)(char))_cffi_exports[15]) -#define _cffi_from_c_deref \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) -#define _cffi_to_c \ - ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) -#define _cffi_from_c_struct \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) -#define _cffi_to_c_wchar_t \ - ((wchar_t(*)(PyObject *))_cffi_exports[19]) -#define _cffi_from_c_wchar_t \ - ((PyObject *(*)(wchar_t))_cffi_exports[20]) -#define _cffi_to_c_long_double \ - ((long double(*)(PyObject *))_cffi_exports[21]) -#define _cffi_to_c__Bool \ - ((_Bool(*)(PyObject *))_cffi_exports[22]) -#define _cffi_prepare_pointer_call_argument \ - ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) -#define _cffi_convert_array_from_object \ - ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) -#define _CFFI_NUM_EXPORTS 25 - -typedef struct _ctypedescr CTypeDescrObject; - -static void *_cffi_exports[_CFFI_NUM_EXPORTS]; -static PyObject *_cffi_types, *_cffi_VerificationError; - -static int _cffi_setup_custom(PyObject *lib); /* forward */ - -static PyObject *_cffi_setup(PyObject *self, PyObject *args) -{ - PyObject *library; - int was_alive = (_cffi_types != NULL); - (void)self; /* unused */ - if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, - &library)) - return NULL; - Py_INCREF(_cffi_types); - Py_INCREF(_cffi_VerificationError); - if (_cffi_setup_custom(library) < 0) - return NULL; - return PyBool_FromLong(was_alive); -} - -union _cffi_union_alignment_u { - unsigned char m_char; - unsigned short m_short; - unsigned int m_int; - unsigned long m_long; - unsigned long long m_longlong; - float m_float; - double m_double; - long double m_longdouble; -}; - -struct _cffi_freeme_s { - struct _cffi_freeme_s *next; - union _cffi_union_alignment_u alignment; -}; - -#ifdef __GNUC__ - __attribute__((unused)) -#endif -static int _cffi_convert_array_argument(CTypeDescrObject *ctptr, PyObject *arg, - char **output_data, Py_ssize_t datasize, - struct _cffi_freeme_s **freeme) -{ - char *p; - if (datasize < 0) - return -1; - - p = *output_data; - if (p == NULL) { - struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( - offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); - if (fp == NULL) - return -1; - fp->next = *freeme; - *freeme = fp; - p = *output_data = (char *)&fp->alignment; - } - memset((void *)p, 0, (size_t)datasize); - return _cffi_convert_array_from_object(p, ctptr, arg); -} - -#ifdef __GNUC__ - __attribute__((unused)) -#endif -static void _cffi_free_array_arguments(struct _cffi_freeme_s *freeme) -{ - do { - void *p = (void *)freeme; - freeme = freeme->next; - PyObject_Free(p); - } while (freeme != NULL); -} - -static int _cffi_init(void) -{ - PyObject *module, *c_api_object = NULL; - - module = PyImport_ImportModule("_cffi_backend"); - if (module == NULL) - goto failure; - - c_api_object = PyObject_GetAttrString(module, "_C_API"); - if (c_api_object == NULL) - goto failure; - if (!PyCapsule_CheckExact(c_api_object)) { - PyErr_SetNone(PyExc_ImportError); - goto failure; - } - memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), - _CFFI_NUM_EXPORTS * sizeof(void *)); - - Py_DECREF(module); - Py_DECREF(c_api_object); - return 0; - - failure: - Py_XDECREF(module); - Py_XDECREF(c_api_object); - return -1; -} - -#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) - -/**********/ - - - -#include -#define TK_HEX_VERSION ((TK_MAJOR_VERSION << 24) | (TK_MINOR_VERSION << 16) | (TK_RELEASE_LEVEL << 8) | (TK_RELEASE_SERIAL << 0)) -#ifdef TCL_WIDE_INT_TYPE -#define HAVE_WIDE_INT_TYPE 1 -#else -#define HAVE_WIDE_INT_TYPE 0 -#endif - - -static int _cffi_const_HAVE_WIDE_INT_TYPE(PyObject *lib) -{ - PyObject *o; - int res; - o = _cffi_from_c_int_const(HAVE_WIDE_INT_TYPE); - if (o == NULL) - return -1; - res = PyObject_SetAttrString(lib, "HAVE_WIDE_INT_TYPE", o); - Py_DECREF(o); - if (res < 0) - return -1; - return ((void)lib,0); -} - -static int _cffi_const_TK_HEX_VERSION(PyObject *lib) -{ - PyObject *o; - int res; - o = _cffi_from_c_int_const(TK_HEX_VERSION); - if (o == NULL) - return -1; - res = PyObject_SetAttrString(lib, "TK_HEX_VERSION", o); - Py_DECREF(o); - if (res < 0) - return -1; - return _cffi_const_HAVE_WIDE_INT_TYPE(lib); -} - -static int _cffi_setup_custom(PyObject *lib) -{ - return _cffi_const_TK_HEX_VERSION(lib); -} - -static PyMethodDef _cffi_methods[] = { - {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -#if PY_MAJOR_VERSION >= 3 - -static struct PyModuleDef _cffi_module_def = { - PyModuleDef_HEAD_INIT, - "_cffi__x993bc6b9x15ee1dff", - NULL, - -1, - _cffi_methods, - NULL, NULL, NULL, NULL -}; - -PyMODINIT_FUNC -PyInit__cffi__x993bc6b9x15ee1dff(void) -{ - PyObject *lib; - lib = PyModule_Create(&_cffi_module_def); - if (lib == NULL) - return NULL; - if (((void)lib,0) < 0 || _cffi_init() < 0) { - Py_DECREF(lib); - return NULL; - } - return lib; -} - -#else - -PyMODINIT_FUNC -init_cffi__x993bc6b9x15ee1dff(void) -{ - PyObject *lib; - lib = Py_InitModule("_cffi__x993bc6b9x15ee1dff", _cffi_methods); - if (lib == NULL) - return; - if (((void)lib,0) < 0 || _cffi_init() < 0) - return; - return; -} - -#endif From 3618399f91ddd42bfc8ea54fcbb1d76cc92b6684 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Mon, 4 Aug 2025 16:48:26 +0100 Subject: [PATCH 4/6] add automatic setup helper for GraalPy Tkinter support on macOS - Added setup_tkinter() function to handle missing _tkinter module - Automatically installs pip dependencies and system requirements --- graalpython/lib-python/3/tkinter/__init__.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/graalpython/lib-python/3/tkinter/__init__.py b/graalpython/lib-python/3/tkinter/__init__.py index a5e7da0680..6b3b7aba4d 100644 --- a/graalpython/lib-python/3/tkinter/__init__.py +++ b/graalpython/lib-python/3/tkinter/__init__.py @@ -34,7 +34,6 @@ import enum import sys import types -import traceback def _setup_tkinter(): import os @@ -72,15 +71,10 @@ def _setup_tkinter(): try: import _tkinter -except ModuleNotFoundError as e: - tb = traceback.format_exc() - missing_tklib = "_tkinter.tklib_cffi" in tb or "_tkinter" in tb - - if missing_tklib: - _setup_tkinter() - import _tkinter - else: - TclError = _tkinter.TclError +except Exception: + _setup_tkinter() + import _tkinter + TclError = _tkinter.TclError from tkinter.constants import * import re From 0667d478de12ae36dd1395af3a91609fc1f27b94 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Tue, 5 Aug 2025 10:31:10 +0100 Subject: [PATCH 5/6] replace 'mx python' with sys.executable --- graalpython/lib-python/3/tkinter/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/graalpython/lib-python/3/tkinter/__init__.py b/graalpython/lib-python/3/tkinter/__init__.py index 6b3b7aba4d..1eb02ab482 100644 --- a/graalpython/lib-python/3/tkinter/__init__.py +++ b/graalpython/lib-python/3/tkinter/__init__.py @@ -34,16 +34,18 @@ import enum import sys import types +import os +import subprocess +import sys + def _setup_tkinter(): - import os - import subprocess print("GraalPy requires additional setup to enable Tkinter support on macOS.\n") print("Ensure pip and required packages are installed:") - print(" mx python -m ensurepip") - print(" mx python -m pip install cffi setuptools\n") + print(f" {sys.executable} -m ensurepip") + print(f" {sys.executable} -m pip install cffi setuptools\n") print("Install system dependencies:") print(" brew install tcl-tk@8") @@ -52,8 +54,8 @@ def _setup_tkinter(): resp = input("Would you like to run pip setup and build now? [Y/n]: ").strip().lower() if resp in ("", "y", "yes"): try: - subprocess.check_call(["mx", "python", "-m", "ensurepip"]) - subprocess.check_call(["mx", "python", "-m", "pip", "install", "cffi", "setuptools"]) + subprocess.check_call([sys.executable, "-m", "ensurepip"]) + subprocess.check_call([sys.executable, "-m", "pip", "install", "cffi", "setuptools"]) subprocess.check_call(["brew", "install", "tcl-tk@8"]) current_dir = os.path.dirname(__file__) tklib_build_path = os.path.abspath(os.path.join( @@ -61,7 +63,7 @@ def _setup_tkinter(): "../../../../../darwin-aarch64/GRAALPY_JVM_STANDALONE/lib/python3.12/_tkinter/tklib_build.py" )) - subprocess.check_call(["mx", "python", tklib_build_path]) + subprocess.check_call([sys.executable, tklib_build_path]) except Exception as build_err: raise build_err else: From b883a76e752a1f9cf737e8aba8e2694e033ab016 Mon Sep 17 00:00:00 2001 From: RachidaTanassat Date: Tue, 5 Aug 2025 19:03:05 +0100 Subject: [PATCH 6/6] Refactor Tkinter setup script: dynamic path resolution, package installation, and system dependency handling --- graalpython/lib-python/3/tkinter/__init__.py | 93 +++++++++++++------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/graalpython/lib-python/3/tkinter/__init__.py b/graalpython/lib-python/3/tkinter/__init__.py index 1eb02ab482..f1a1899fe0 100644 --- a/graalpython/lib-python/3/tkinter/__init__.py +++ b/graalpython/lib-python/3/tkinter/__init__.py @@ -36,45 +36,74 @@ import types import os import subprocess -import sys - - -def _setup_tkinter(): - - print("GraalPy requires additional setup to enable Tkinter support on macOS.\n") - - print("Ensure pip and required packages are installed:") - print(f" {sys.executable} -m ensurepip") - print(f" {sys.executable} -m pip install cffi setuptools\n") +import importlib.util +import shutil +import platform - print("Install system dependencies:") - print(" brew install tcl-tk@8") +def ensure_installed(name, *extra): + try: + return importlib.import_module(name) + except ImportError: + subprocess.check_call([sys.executable, "-m", "pip", "install", name, *extra]) + return importlib.import_module(name) + +def install_system_dependencies(): + if sys.platform == "darwin": + if not shutil.which("brew"): + print("Homebrew not found.") + install_brew = input("Install Homebrew now? [Y/n]: ").strip().low() + if install_brew in ("", "y", "yes"): + subprocess.check_call([ + "/bin/bash", "-c", + "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ]) + else: + print("Cannot continue without Homebrew. Please install it and rerun.") + sys.exit(1) - if sys.stdin.isatty(): - resp = input("Would you like to run pip setup and build now? [Y/n]: ").strip().lower() - if resp in ("", "y", "yes"): - try: - subprocess.check_call([sys.executable, "-m", "ensurepip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", "cffi", "setuptools"]) - subprocess.check_call(["brew", "install", "tcl-tk@8"]) - current_dir = os.path.dirname(__file__) - tklib_build_path = os.path.abspath(os.path.join( - current_dir, - "../../../../../darwin-aarch64/GRAALPY_JVM_STANDALONE/lib/python3.12/_tkinter/tklib_build.py" - )) - - subprocess.check_call([sys.executable, tklib_build_path]) - except Exception as build_err: - raise build_err + subprocess.check_call(["brew", "install", "tcl-tk@8"]) else: - print("\n Run in an interactive terminal to auto-run pip setup and build.") - - + subprocess.check_call(["sudo", "apt", "install", "-y", "tcl8.6-dev", "tk8.6-dev"]) + +def run_tkinter_build_script(): + current_dir = os.path.dirname(__file__) + system = platform.system().lower() + machine = platform.machine().lower() + if machine == 'x86_64': + machine = 'x64' + elif machine == 'arm64' or machine == 'aarch64': + machine = 'aarch64' + + platform_id = f"{system}-{machine}" + + py_version = f"{sys.version_info.major}.{sys.version_info.minor}" + base_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "..", "..", "..")) + script_path = os.path.join( + base_dir, + platform_id, + "GRAALPY_JVM_STANDALONE", + "lib", + f"python{py_version}", + "_tkinter", + "tklib_build.py" + ) + if not os.path.exists(script_path): + raise FileNotFoundError(f"Build script not found: {script_path}") + subprocess.check_call([sys.executable, script_path]) + +def setup_tkinter(): + + print("Checking required Python packages...") + for pkg in ( "cffi", "setuptools" ): + ensure_installed(pkg) + + install_system_dependencies() + run_tkinter_build_script() try: import _tkinter except Exception: - _setup_tkinter() + setup_tkinter() import _tkinter TclError = _tkinter.TclError