diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24c8cdffe0..44b6241834 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -387,6 +387,23 @@ jobs: rmdir panda3d-1.10.14 (cd thirdparty/darwin-libs-a && rm -rf rocket) + - name: Set up Python 3.13 + if: runner.os != 'Windows' + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Build Python 3.13 + if: runner.os != 'Windows' + shell: bash + run: | + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 + - name: Test Python 3.13 + if: runner.os != 'Windows' + shell: bash + run: | + python -m pip install pytest setuptools + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: diff --git a/direct/src/dcparser/dcField_ext.cxx b/direct/src/dcparser/dcField_ext.cxx index e2c1167811..a10fffd456 100644 --- a/direct/src/dcparser/dcField_ext.cxx +++ b/direct/src/dcparser/dcField_ext.cxx @@ -278,8 +278,9 @@ get_pystr(PyObject *value) { return result; } - if (value->ob_type != nullptr) { - PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type)); + PyTypeObject *type = Py_TYPE(value); + if (type != nullptr) { + PyObject *typestr = PyObject_Str((PyObject *)type); if (typestr != nullptr) { std::string result = PyUnicode_AsUTF8(typestr); Py_DECREF(typestr); diff --git a/direct/src/showbase/DistancePhasedNode.py b/direct/src/showbase/DistancePhasedNode.py index fc4c5a471b..5fc12769e0 100755 --- a/direct/src/showbase/DistancePhasedNode.py +++ b/direct/src/showbase/DistancePhasedNode.py @@ -87,7 +87,8 @@ class DistancePhasedNode(PhasedObject, DirectObject, NodePath): """ Reuse abandoned ids. """ - DistancePhasedNode.__InstanceDeque.append(id) + if DistancePhasedNode is not None: + DistancePhasedNode.__InstanceDeque.append(id) def __init__(self, name, phaseParamMap = {}, autoCleanup = True, diff --git a/dtool/src/dtoolutil/filename_ext.cxx b/dtool/src/dtoolutil/filename_ext.cxx index cc67d9dd2e..6de25dee3d 100644 --- a/dtool/src/dtoolutil/filename_ext.cxx +++ b/dtool/src/dtoolutil/filename_ext.cxx @@ -47,7 +47,7 @@ __init__(PyObject *path) { return; } - if (Py_TYPE(path) == &Dtool_Filename._PyType) { + if (Py_IS_TYPE(path, Dtool_GetPyTypeObject(&Dtool_Filename))) { // Copy constructor. *_this = *(Filename *)DtoolInstance_VOID_PTR(path); return; diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 1ef0b1eb35..b93978ffbd 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -65,7 +65,7 @@ typedef int Py_ssize_t; /* Python 2.6 */ -#ifndef Py_TYPE +#if PY_VERSION_HEX < 0x02060000 # define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) #endif @@ -221,8 +221,12 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) { /* Python 3.9 */ +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +# define Py_IS_TYPE(ob, type) (Py_TYPE((PyObject *)ob) == type) +#endif + #ifndef PyCFunction_CheckExact -# define PyCFunction_CheckExact(op) (Py_TYPE(op) == &PyCFunction_Type) +# define PyCFunction_CheckExact(op) (Py_IS_TYPE(op, &PyCFunction_Type)) #endif #if PY_VERSION_HEX < 0x03090000 @@ -281,6 +285,28 @@ ALWAYS_INLINE PyObject *Py_XNewRef(PyObject *obj) { } #endif +/* Python 3.10 */ + +#if PY_VERSION_HEX < 0x030A0000 +INLINE int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) { + int ret = PyModule_AddObject(module, name, value); + if (ret == 0) { + Py_INCREF(value); + } + return ret; +} + +ALWAYS_INLINE PyObject *Py_NewRef(PyObject *obj) { + Py_INCREF(obj); + return obj; +} + +ALWAYS_INLINE PyObject *Py_XNewRef(PyObject *obj) { + Py_XINCREF(obj); + return obj; +} +#endif + /* Python 3.12 */ #if PY_VERSION_HEX < 0x030C0000 diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 8e3614b713..fd2f8d621c 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -321,7 +321,7 @@ static PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObj subtype->tp_name); } - if (Py_TYPE(arg) == subtype) { + if (Py_IS_TYPE(arg, subtype)) { return Py_NewRef(arg); } diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 4b1339adae..446c4d6770 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1723,7 +1723,7 @@ def CompileLink(dll, obj, opts): if "PYTHON" not in opts: pythonv = SDK["PYTHONVERSION"].replace('.', '') if optlevel <= 2: - cmd += ' /NOD:{}d.lib'.format(pythonv) + cmd += ' /NOD:{}_d.lib'.format(pythonv) else: cmd += ' /NOD:{}.lib'.format(pythonv) diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 30c89b4f50..7b1a522ab3 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -2129,6 +2129,10 @@ def SdkLocatePython(prefer_thirdparty_python=False): gil_disabled = locations.get_config_var("Py_GIL_DISABLED") if gil_disabled and int(gil_disabled): SDK["PYTHONEXEC"] += "3.13t" + abiflags = "t" + DefSymbol("PYTHON", "Py_GIL_DISABLED", "1") + else: + abiflags = "" if (GetOptimize() <= 2): SDK["PYTHONEXEC"] += "_d.exe" @@ -2140,11 +2144,11 @@ def SdkLocatePython(prefer_thirdparty_python=False): # Determine which version it is by checking which dll is in the directory. if (GetOptimize() <= 2): - py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]_d.dll") + \ - glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9]_d.dll") + py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]" + abiflags + "_d.dll") + \ + glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9]" + abiflags + "_d.dll") else: - py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9].dll") + \ - glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9].dll") + py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]" + abiflags + ".dll") + \ + glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9]" + abiflags + ".dll") if len(py_dlls) == 0: exit("Could not find the Python dll in %s." % (SDK["PYTHON"])) @@ -2155,7 +2159,7 @@ def SdkLocatePython(prefer_thirdparty_python=False): py_dllver = py_dll.strip(".DHLNOPTY_dhlnopty") ver = py_dllver[0] + '.' + py_dllver[1:] - SDK["PYTHONVERSION"] = "python" + ver + SDK["PYTHONVERSION"] = "python" + ver + abiflags os.environ["PYTHONHOME"] = SDK["PYTHON"] running_ver = '%d.%d' % sys.version_info[:2] @@ -3389,7 +3393,14 @@ def GetPythonABI(): if soabi: return soabi - return 'cpython-%d%d' % (sys.version_info[:2]) + soabi = 'cpython-%d%d' % (sys.version_info[:2]) + + if sys.version_info >= (3, 13): + gil_disabled = locations.get_config_var("Py_GIL_DISABLED") + if gil_disabled and int(gil_disabled): + return soabi + 't' + + return soabi def CalcLocation(fn, ipath): if fn.startswith("panda3d/") and fn.endswith(".py"): diff --git a/panda/src/event/asyncFuture_ext.cxx b/panda/src/event/asyncFuture_ext.cxx index 173d215361..5ec7d080cb 100644 --- a/panda/src/event/asyncFuture_ext.cxx +++ b/panda/src/event/asyncFuture_ext.cxx @@ -175,7 +175,7 @@ set_result(PyObject *result) { else if (DtoolInstance_Check(result)) { // If this is a Python subclass of a C++ type, fall through to below, since // we don't want to lose that extra information. - if (Py_TYPE(result) == (PyTypeObject *)DtoolInstance_TYPE(result)) { + if (Py_IS_TYPE(result, Dtool_GetPyTypeObject(DtoolInstance_TYPE(result)))) { void *ptr; if ((ptr = DtoolInstance_UPCAST(result, Dtool_TypedWritableReferenceCount))) { _this->set_result((TypedWritableReferenceCount *)ptr); diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index 4c00e2db7b..d2515a9312 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -300,13 +300,13 @@ __setattr__(PyObject *self, PyObject *attr, PyObject *v) { if (!PyUnicode_Check(attr)) { PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", - attr->ob_type->tp_name); + Py_TYPE(attr)->tp_name); return -1; } PyObject *descr = _PyType_Lookup(Py_TYPE(self), attr); if (descr != nullptr) { - descrsetfunc f = descr->ob_type->tp_descr_set; + descrsetfunc f = Py_TYPE(descr)->tp_descr_set; if (f != nullptr) { return f(descr, self, v); } @@ -469,7 +469,7 @@ cancel() { #endif // Shortcut for unextended AsyncFuture. - if (Py_TYPE(_fut_waiter) == (PyTypeObject *)&Dtool_AsyncFuture) { + if (Py_IS_TYPE(_fut_waiter, Dtool_GetPyTypeObject(&Dtool_AsyncFuture))) { AsyncFuture *fut = (AsyncFuture *)DtoolInstance_VOID_PTR(_fut_waiter); if (!fut->done()) { fut->cancel(); @@ -717,7 +717,7 @@ do_python_task() { _retrieved_exception = false; if (task_cat.is_debug()) { - if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) { + if (_exception != nullptr && Py_IS_TYPE(_exception, &PyType_Type)) { task_cat.debug() << *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n"; } else { @@ -862,7 +862,7 @@ do_python_task() { PyMethodDef *meth = nullptr; if (PyCFunction_Check(result)) { meth = ((PyCFunctionObject *)result)->m_ml; - } else if (Py_TYPE(result) == &PyMethodDescr_Type) { + } else if (Py_IS_TYPE(result, &PyMethodDescr_Type)) { meth = ((PyMethodDescrObject *)result)->d_method; } diff --git a/panda/src/express/datagram_ext.I b/panda/src/express/datagram_ext.I index 70a71a8b44..494a76188e 100644 --- a/panda/src/express/datagram_ext.I +++ b/panda/src/express/datagram_ext.I @@ -47,7 +47,7 @@ __reduce__() const { } extern struct Dtool_PyTypedObject Dtool_Datagram; - PyObject *tp = (PyObject *)&Dtool_Datagram._PyType; + PyObject *tp = (PyObject *)Dtool_GetPyTypeObject(&Dtool_Datagram); PyObject *result = PyTuple_New(2); PyTuple_SET_ITEM(result, 0, Py_NewRef(tp)); diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index 0fb4230d1d..849f993cba 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -96,7 +96,7 @@ __init__(PyObject *self, PyObject *source) { // Now construct the internal list by copying the elements one-at-a-time // from Python. - PyObject *dict = DtoolInstance_TYPE(self)->_PyType.tp_dict; + PyObject *dict = Dtool_GetPyTypeObject(DtoolInstance_TYPE(self))->tp_dict; PyObject *push_back = PyDict_GetItemString(dict, "push_back"); if (push_back == nullptr) { PyErr_BadArgument(); diff --git a/panda/src/gobj/internalName_ext.cxx b/panda/src/gobj/internalName_ext.cxx index c7a145f23c..ab8e49eec8 100644 --- a/panda/src/gobj/internalName_ext.cxx +++ b/panda/src/gobj/internalName_ext.cxx @@ -74,7 +74,7 @@ PyObject *Extension:: __reduce__() const { std::string name = _this->get_name(); return Py_BuildValue("(N(s#))", - PyObject_GetAttrString((PyObject *)&Dtool_InternalName._PyType, "make"), name.c_str(), name.size()); + PyObject_GetAttrString((PyObject *)Dtool_GetPyTypeObject(&Dtool_InternalName), "make"), name.c_str(), name.size()); } #endif // HAVE_PYTHON diff --git a/panda/src/linmath/lvecBase2_ext_src.I b/panda/src/linmath/lvecBase2_ext_src.I index f860917257..adb8e793e9 100644 --- a/panda/src/linmath/lvecBase2_ext_src.I +++ b/panda/src/linmath/lvecBase2_ext_src.I @@ -183,7 +183,7 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) { #endif } else { PyErr_Format(PyExc_ValueError, "'%.200s' object is not iterable", - assign->ob_type->tp_name); + Py_TYPE(assign)->tp_name); } return -1; } diff --git a/panda/src/linmath/lvecBase3_ext_src.I b/panda/src/linmath/lvecBase3_ext_src.I index 67bc4b65b0..07a83a010d 100644 --- a/panda/src/linmath/lvecBase3_ext_src.I +++ b/panda/src/linmath/lvecBase3_ext_src.I @@ -187,7 +187,7 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) { #endif } else { PyErr_Format(PyExc_ValueError, "'%.200s' object is not iterable", - assign->ob_type->tp_name); + Py_TYPE(assign)->tp_name); } return -1; } diff --git a/panda/src/linmath/lvecBase4_ext_src.I b/panda/src/linmath/lvecBase4_ext_src.I index 9b396af666..40647bff42 100644 --- a/panda/src/linmath/lvecBase4_ext_src.I +++ b/panda/src/linmath/lvecBase4_ext_src.I @@ -196,7 +196,7 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) { #endif } else { PyErr_Format(PyExc_ValueError, "'%.200s' object is not iterable", - assign->ob_type->tp_name); + Py_TYPE(assign)->tp_name); } return -1; } diff --git a/panda/src/ode/odeSpace_ext.cxx b/panda/src/ode/odeSpace_ext.cxx index 4c6fb56161..27f4cbbb6a 100644 --- a/panda/src/ode/odeSpace_ext.cxx +++ b/panda/src/ode/odeSpace_ext.cxx @@ -72,7 +72,7 @@ collide(PyObject* arg, PyObject* callback) { nassertr(callback != nullptr, -1); if (!PyCallable_Check(callback)) { - PyErr_Format(PyExc_TypeError, "'%s' object is not callable", callback->ob_type->tp_name); + PyErr_Format(PyExc_TypeError, "'%s' object is not callable", Py_TYPE(callback)->tp_name); return -1; } else if (_this->get_id() == nullptr) { diff --git a/panda/src/ode/odeUtil_ext.cxx b/panda/src/ode/odeUtil_ext.cxx index e3115bfda6..545fbb6f1a 100644 --- a/panda/src/ode/odeUtil_ext.cxx +++ b/panda/src/ode/odeUtil_ext.cxx @@ -28,7 +28,7 @@ int Extension:: collide2(const OdeGeom &geom1, const OdeGeom &geom2, PyObject* arg, PyObject* callback) { nassertr(callback != nullptr, -1); if (!PyCallable_Check(callback)) { - PyErr_Format(PyExc_TypeError, "'%s' object is not callable", callback->ob_type->tp_name); + PyErr_Format(PyExc_TypeError, "'%s' object is not callable", Py_TYPE(callback)->tp_name); return -1; } else { _python_callback = Py_XNewRef(callback); diff --git a/panda/src/pgraph/nodePathCollection_ext.cxx b/panda/src/pgraph/nodePathCollection_ext.cxx index 33cedfb5c3..de0267e589 100644 --- a/panda/src/pgraph/nodePathCollection_ext.cxx +++ b/panda/src/pgraph/nodePathCollection_ext.cxx @@ -75,7 +75,7 @@ __reduce__(PyObject *self) const { // object whose constructor we should call (e.g. this), and the arguments // necessary to reconstruct this object. - PyObject *this_class = (PyObject *)self->ob_type; + PyObject *this_class = (PyObject *)Py_TYPE(self); if (this_class == nullptr) { return nullptr; } diff --git a/panda/src/putil/bamReader_ext.cxx b/panda/src/putil/bamReader_ext.cxx index 308028cea3..84c5060e1e 100644 --- a/panda/src/putil/bamReader_ext.cxx +++ b/panda/src/putil/bamReader_ext.cxx @@ -85,7 +85,7 @@ static TypedWritable *factory_callback(const FactoryParams ¶ms){ Py_DECREF(result); } else if (DtoolInstance_TYPE(result) == &Dtool_TypedWritable && - Py_TYPE(result) != &Dtool_TypedWritable._PyType) { + !Py_IS_TYPE(result, Dtool_GetPyTypeObject(&Dtool_TypedWritable))) { // It is a custom subclass of TypedWritable, so we have to keep it // alive, and decrement it in finalize(), see typedWritable_ext.cxx. manager->register_finalize(ptr); diff --git a/panda/src/putil/pythonCallbackObject.cxx b/panda/src/putil/pythonCallbackObject.cxx index 454c9bdaf5..bd70b13641 100644 --- a/panda/src/putil/pythonCallbackObject.cxx +++ b/panda/src/putil/pythonCallbackObject.cxx @@ -87,7 +87,7 @@ get_function() { */ PyObject *PythonCallbackObject:: __reduce__() const { - return Py_BuildValue("O(O)", (PyObject *)&Dtool_PythonCallbackObject._PyType, _function); + return Py_BuildValue("O(O)", (PyObject *)Dtool_GetPyTypeObject(&Dtool_PythonCallbackObject), _function); } /** diff --git a/panda/src/putil/typedWritable_ext.cxx b/panda/src/putil/typedWritable_ext.cxx index 0fc665e0e6..0a328e228c 100644 --- a/panda/src/putil/typedWritable_ext.cxx +++ b/panda/src/putil/typedWritable_ext.cxx @@ -134,10 +134,10 @@ wrap_typed_writable(void *from_this, PyTypeObject *from_type) { nassertr(from_type != nullptr, nullptr); TypedWritableProxy *to_this; - if (from_type == &Dtool_TypedWritable._PyType) { + if (from_type == Dtool_GetPyTypeObject(&Dtool_TypedWritable)) { to_this = (TypedWritableProxy *)(TypedWritable *)from_this; } - else if (from_type == (PyTypeObject *)&Dtool_TypedObject._PyType) { + else if (from_type == Dtool_GetPyTypeObject(&Dtool_TypedObject)) { to_this = (TypedWritableProxy *)(TypedObject *)from_this; } else {