* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#ifndef BASE_COMMON_PYTHON_RUNTIME_GE_PYTHON_RUNTIME_MANAGER_HELPER_H_
#define BASE_COMMON_PYTHON_RUNTIME_GE_PYTHON_RUNTIME_MANAGER_HELPER_H_
#include <dlfcn.h>
#include <string>
#include "framework/common/debug/ge_log.h"
#include "ge/ge_api_error_codes.h"
#include "graph/utils/file_utils.h"
namespace ge {
using PyIsInitializedFn = int (*)();
using PyInitializeFn = void (*)();
using PyFinalizeFn = void (*)();
using PyGetVersionFn = const char *(*)();
using PyEvalInitThreadsFn = void (*)();
using PyEvalThreadsInitializedFn = int (*)();
using PyEvalSaveThreadFn = void *(*)();
using PyEvalRestoreThreadFn = void (*)(void *);
using PyGILStateCheckFn = int (*)();
struct PythonCApi {
PyIsInitializedFn py_is_initialized{nullptr};
PyInitializeFn py_initialize{nullptr};
PyFinalizeFn py_finalize{nullptr};
PyGetVersionFn py_get_version{nullptr};
PyEvalInitThreadsFn py_eval_init_threads{nullptr};
PyEvalThreadsInitializedFn py_eval_threads_initialized{nullptr};
PyEvalSaveThreadFn py_eval_save_thread{nullptr};
PyEvalRestoreThreadFn py_eval_restore_thread{nullptr};
PyGILStateCheckFn py_gil_state_check{nullptr};
};
struct PythonProbeResult {
std::string python_command;
std::string python_tag;
std::string libpython_path;
};
extern PythonCApi g_python_api;
constexpr const char *kPyIsInitializedSymbol = "Py_IsInitialized";
constexpr const char *kPyInitializeSymbol = "Py_Initialize";
constexpr const char *kPyFinalizeSymbol = "Py_Finalize";
constexpr const char *kPyGetVersionSymbol = "Py_GetVersion";
constexpr const char *kPyEvalInitThreadsSymbol = "PyEval_InitThreads";
constexpr const char *kPyEvalThreadsInitializedSymbol = "PyEval_ThreadsInitialized";
constexpr const char *kPyEvalSaveThreadSymbol = "PyEval_SaveThread";
constexpr const char *kPyEvalRestoreThreadSymbol = "PyEval_RestoreThread";
constexpr const char *kPyGILStateCheckSymbol = "PyGILState_Check";
constexpr const char *kPythonRuntimeProbeScript =
" -c \"import sys; print('cp%d%d' % sys.version_info[:2])\" 2>/dev/null";
constexpr const char *kLibpythonProbeScript =
" -c \"import os, sys, sysconfig\n"
"version = sysconfig.get_config_var('VERSION') or ''\n"
"lib_version = version + ('m' if sys.version_info[:2] <= (3, 7) else '') if version else ''\n"
"libdirs = []\n"
"for item in [os.path.join(sys.prefix, 'lib'), os.path.join(sys.exec_prefix, 'lib'), "
"os.path.abspath(os.path.join(os.path.dirname(sys.executable), '..', 'lib')), "
"sysconfig.get_config_var('LIBDIR') or '']:\n"
" if item and item not in libdirs:\n"
" libdirs.append(item)\n"
"candidates = [sysconfig.get_config_var('LDLIBRARY'), sysconfig.get_config_var('INSTSONAME'), "
"sysconfig.get_config_var('LIBRARY')]\n"
"candidates.extend([('libpython%s.so.1.0' % lib_version) if lib_version else '', "
"('libpython%s.so' % lib_version) if lib_version else ''])\n"
"seen = []\n"
"for item in candidates:\n"
" if item and item not in seen:\n"
" seen.append(item)\n"
"for item in seen:\n"
" if '.so' not in item:\n"
" continue\n"
" for directory in libdirs:\n"
" path = os.path.join(directory, item)\n"
" if os.path.exists(path):\n"
" print(path)\n"
" sys.exit(0)\n"
"for item in seen:\n"
" if '.so' in item:\n"
" print(item)\n"
" break\" 2>/dev/null";
constexpr int kOpenFlags = RTLD_NOW | RTLD_GLOBAL;
inline std::string FirstLine(const std::string &text) {
const auto pos = text.find('\n');
if (pos == std::string::npos) {
return text;
}
return text.substr(0U, pos);
}
inline bool ReadCommandOutput(const std::string &command, std::string &output) {
FILE *fp = popen(command.c_str(), "r");
if (fp == nullptr) {
return false;
}
char buffer[256] = {0};
while (fgets(buffer, sizeof(buffer), fp) != nullptr) {
output += buffer;
}
const auto ret = pclose(fp);
return (ret == 0) && (!output.empty());
}
inline bool ProbePythonRuntimeFromCommand(const std::string &python_command, PythonProbeResult &result) {
if (python_command.empty()) {
return false;
}
std::string output;
if (!ReadCommandOutput(python_command + kPythonRuntimeProbeScript, output)) {
return false;
}
const auto python_tag = FirstLine(output);
if (python_tag.empty()) {
return false;
}
result.python_command = python_command;
result.python_tag = python_tag;
return true;
}
inline std::string ResolveLibpythonPathFromCommand(const std::string &python_command) {
if (python_command.empty()) {
return "";
}
std::string output;
if (!ReadCommandOutput(python_command + kLibpythonProbeScript, output)) {
return "";
}
const auto libpython_path = FirstLine(output);
if (libpython_path.empty()) {
return "";
}
const auto real_path = RealPath(libpython_path.c_str());
return real_path.empty() ? libpython_path : real_path;
}
inline bool ProbeRuntimeCandidate(const std::string &candidate, PythonProbeResult &probed) {
if (!ProbePythonRuntimeFromCommand(candidate, probed)) {
return false;
}
probed.libpython_path = ResolveLibpythonPathFromCommand(probed.python_command);
if (probed.libpython_path.empty()) {
GELOGW("[GePythonRuntime] Skip python command[%s] because libpython path is unresolved.",
probed.python_command.c_str());
return false;
}
return true;
}
template <typename T>
inline bool ResolvePythonSymbol(void *handle, const char *symbol, const bool log_failure, T &func) {
func = reinterpret_cast<T>(dlsym(handle, symbol));
if (func != nullptr) {
return true;
}
if (log_failure) {
GELOGW("[GePythonRuntime] Failed to dlsym python symbol[%s].", symbol);
}
return false;
}
inline bool ResolvePythonCApi(void *libpython_handle, const bool log_failure) {
void *handle = (libpython_handle != nullptr) ? libpython_handle : RTLD_DEFAULT;
PythonCApi resolved;
const auto bind_symbol = [handle, log_failure](const char *symbol, auto &func) {
return ResolvePythonSymbol(handle, symbol, log_failure, func);
};
if (!bind_symbol(kPyIsInitializedSymbol, resolved.py_is_initialized) ||
!bind_symbol(kPyInitializeSymbol, resolved.py_initialize) ||
!bind_symbol(kPyFinalizeSymbol, resolved.py_finalize) ||
!bind_symbol(kPyGetVersionSymbol, resolved.py_get_version) ||
!bind_symbol(kPyEvalInitThreadsSymbol, resolved.py_eval_init_threads) ||
!bind_symbol(kPyEvalThreadsInitializedSymbol, resolved.py_eval_threads_initialized) ||
!bind_symbol(kPyEvalSaveThreadSymbol, resolved.py_eval_save_thread) ||
!bind_symbol(kPyEvalRestoreThreadSymbol, resolved.py_eval_restore_thread) ||
!bind_symbol(kPyGILStateCheckSymbol, resolved.py_gil_state_check)) {
return false;
}
g_python_api = resolved;
return true;
}
inline void ResetPythonCApi() {
g_python_api = PythonCApi{};
}
inline bool EnsureLibpythonLoaded(void **handle) {
if (handle == nullptr) {
return false;
}
if (*handle != nullptr) {
return true;
}
bool has_probe_result = false;
for (const std::string candidate : {"python3", "python"}) {
PythonProbeResult probe_result;
if (!ProbeRuntimeCandidate(candidate, probe_result)) {
continue;
}
has_probe_result = true;
void *opened_handle = dlopen(probe_result.libpython_path.c_str(), kOpenFlags);
if (opened_handle == nullptr) {
const char *open_error = dlerror();
std::string open_error_message;
if (open_error != nullptr) {
open_error_message = open_error;
}
GELOGW("[GePythonRuntime] dlopen libpython[%s] failed: %s, try next candidate.",
probe_result.libpython_path.c_str(), open_error_message.c_str());
continue;
}
*handle = opened_handle;
GELOGI("[GePythonRuntime] Loaded libpython[%s] for python tag[%s].", probe_result.libpython_path.c_str(),
probe_result.python_tag.c_str());
return true;
}
if (!has_probe_result) {
GELOGW("[GePythonRuntime] Probe target python runtime failed.");
return false;
}
GELOGW("[GePythonRuntime] Load libpython failed for all probed python runtime candidates.");
return false;
}
}
#endif