From 0f43ec3158225092f6a02422eb90c56421326570 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Robert-Andr=C3=A9=20Mauchin?= <zebob.m@gmail.com>
Date: Tue, 18 Jun 2024 22:05:34 +0200
Subject: [PATCH] Changes to build pykrita with Python 3.13

Python 3.11 deprecated Py_SetPath() in 2022 and Python 3.13 removed it.
Instead one needs to use the new PyConfig API (PEP 587) added to Python
3.8.

Fix: #488680
---
 .../extensions/pykrita/plugin/utilities.cpp   | 61 +++++++++++++++++--
 plugins/extensions/pykrita/plugin/utilities.h |  4 +-
 2 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp
index 4f58183238..1e497b2681 100644
--- a/plugins/extensions/pykrita/plugin/utilities.cpp
+++ b/plugins/extensions/pykrita/plugin/utilities.cpp
@@ -19,8 +19,10 @@
 #include <cmath>
 #include <Python.h>
 
+#include <QDebug>
 #include <QDir>
 #include <QLibrary>
+#include <QProcessEnvironment>
 #include <QString>
 #include <QStringList>
 #include <QVector>
@@ -412,18 +414,65 @@ bool Python::setPath(const QStringList& scriptPaths)
         joinedPaths = joinedPaths + pathSeparator + originalPath;
     }
     dbgScript << "Setting python paths:" << joinedPaths;
+
 #ifdef Q_OS_WIN
-    QVector<wchar_t> joinedPathsWChars(joinedPaths.size() + 1, 0);
-    joinedPaths.toWCharArray(joinedPathsWChars.data());
-    Py_SetPath(joinedPathsWChars.data());
+    PyStatus status;
+    PyConfig config;
+    PyConfig_InitPythonConfig(&config);
+
+    for (const QString& path : joinedPaths.split(pathSeparator)) {
+        status = PyWideStringList_Append(&config.module_search_paths, path.toStdWString().c_str());
+        if (PyStatus_Exception(status)) {
+            qDebug() << "Error appending to PyWideStringList:" << status.err_msg;
+            dbgScript << "Error appending to PyWideStringList";
+            return false;
+        }
+    }
+
+    config.module_search_paths_set = true;
+    qDebug() << "Set module_search_paths";
+
+    status = Py_InitializeFromConfig(&config);
+    if (PyStatus_Exception(status)) {
+        qDebug() << "Cannot initialize Py_InitializeFromConfig:" << status.err_msg;
+        Py_ExitStatusException(status);
+        PyConfig_Clear(&config);
+        dbgScript << "Cannot initialize Py_InitializeFromConfig config";
+        return false;
+    }
+
+    PyConfig_Clear(&config);
 #else
     if (runningInBundle) {
-        QVector<wchar_t> joinedPathsWChars(joinedPaths.size() + 1, 0);
-        joinedPaths.toWCharArray(joinedPathsWChars.data());
-        Py_SetPath(joinedPathsWChars.data());
+        PyStatus status;
+        PyConfig config;
+        PyConfig_InitPythonConfig(&config);
+
+        for (const QString& path : joinedPaths.split(pathSeparator)) {
+            status = PyWideStringList_Append(&config.module_search_paths, path.toStdWString().c_str());
+            if (PyStatus_Exception(status)) {
+                qDebug() << "Error appending to PyWideStringList:" << status.err_msg;
+                dbgScript << "Error appending to PyWideStringList";
+                return false;
+            }
+        }
+
+        config.module_search_paths_set = true;
+
+        status = Py_InitializeFromConfig(&config);
+        if (PyStatus_Exception(status)) {
+            Py_ExitStatusException(status);
+            qDebug() << "Cannot initialize Py_InitializeFromConfig 2:" << status.err_msg;
+            PyConfig_Clear(&config);
+            dbgScript << "Cannot initialize Py_InitializeFromConfig config";
+            return false;
+        }
+
+        PyConfig_Clear(&config);
     }
     else {
         qputenv("PYTHONPATH", joinedPaths.toLocal8Bit());
+        qDebug() << "Set PYTHONPATH environment variable";
     }
 #endif
     isPythonPathSet = true;
diff --git a/plugins/extensions/pykrita/plugin/utilities.h b/plugins/extensions/pykrita/plugin/utilities.h
index fb309bd0b8..aec47da239 100644
--- a/plugins/extensions/pykrita/plugin/utilities.h
+++ b/plugins/extensions/pykrita/plugin/utilities.h
@@ -81,8 +81,8 @@ public:
     static bool libraryLoad();
 
     /**
-     * Set the Python paths by calling Py_SetPath. This should be called before
-     * initialization to ensure the proper libraries get loaded.
+     * Set the Python paths by calling Py_InitializeFromConfig. This should be
+     * called before initialization to ensure the proper libraries get loaded.
      */
     static bool setPath(const QStringList& scriptPaths);
 
-- 
2.45.2

