Initial clean project import
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { spawn } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Python路径检测工具
|
||||
* 支持在Windows、macOS、Linux等多个系统上检测Python
|
||||
*/
|
||||
|
||||
let cachedPythonPath: string | null = null;
|
||||
|
||||
/**
|
||||
* 获取系统中可用的Python可执行文件路径
|
||||
* 优先级:
|
||||
* 1. 环境变量 PYTHON_PATH
|
||||
* 2. 环境变量 FUNASR_PYTHON_PATH
|
||||
* 3. Windows特定位置
|
||||
* 4. 系统PATH中的python3/python
|
||||
*/
|
||||
export function getPythonPath(): string {
|
||||
// 如果已缓存,直接返回
|
||||
if (cachedPythonPath) {
|
||||
return cachedPythonPath;
|
||||
}
|
||||
|
||||
// 1. 检查环境变量
|
||||
if (process.env.PYTHON_PATH && fs.existsSync(process.env.PYTHON_PATH)) {
|
||||
cachedPythonPath = process.env.PYTHON_PATH;
|
||||
return cachedPythonPath;
|
||||
}
|
||||
|
||||
if (process.env.FUNASR_PYTHON_PATH && fs.existsSync(process.env.FUNASR_PYTHON_PATH)) {
|
||||
cachedPythonPath = process.env.FUNASR_PYTHON_PATH;
|
||||
return cachedPythonPath;
|
||||
}
|
||||
|
||||
// 2. Windows特定位置
|
||||
if (process.platform === 'win32') {
|
||||
const windowsPythonPaths = [
|
||||
// 微软应用商店安装
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python312', 'python.exe'),
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python311', 'python.exe'),
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python310', 'python.exe'),
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python39', 'python.exe'),
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python38', 'python.exe'),
|
||||
// 直接安装
|
||||
'C:\\Python312\\python.exe',
|
||||
'C:\\Python311\\python.exe',
|
||||
'C:\\Python310\\python.exe',
|
||||
'C:\\Python39\\python.exe',
|
||||
'C:\\Python38\\python.exe',
|
||||
// Anaconda/Miniconda
|
||||
path.join(process.env.USERPROFILE || '', 'anaconda3', 'python.exe'),
|
||||
path.join(process.env.USERPROFILE || '', 'miniconda3', 'python.exe'),
|
||||
// WSL
|
||||
path.join(process.env.USERPROFILE || '', 'AppData', 'Local', 'Microsoft', 'WindowsApps', 'python.exe'),
|
||||
];
|
||||
|
||||
for (const pythonPath of windowsPythonPaths) {
|
||||
if (fs.existsSync(pythonPath)) {
|
||||
cachedPythonPath = pythonPath;
|
||||
return cachedPythonPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. macOS/Linux 特定位置
|
||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||
const unixPythonPaths = [
|
||||
'/usr/bin/python3',
|
||||
'/usr/bin/python',
|
||||
'/usr/local/bin/python3',
|
||||
'/usr/local/bin/python',
|
||||
'/opt/homebrew/bin/python3', // M1/M2 Mac
|
||||
path.join(process.env.HOME || '', '.pyenv', 'versions', 'default', 'bin', 'python'),
|
||||
];
|
||||
|
||||
for (const pythonPath of unixPythonPaths) {
|
||||
if (fs.existsSync(pythonPath)) {
|
||||
cachedPythonPath = pythonPath;
|
||||
return cachedPythonPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 默认尝试 python 或 python3
|
||||
// 这会依赖系统 PATH,Windows上可能不工作
|
||||
if (process.platform === 'win32') {
|
||||
cachedPythonPath = 'python';
|
||||
} else {
|
||||
cachedPythonPath = 'python3';
|
||||
}
|
||||
|
||||
return cachedPythonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Python可执行文件是否可用
|
||||
*/
|
||||
export function isValidPythonPath(pythonPath: string): boolean {
|
||||
return fs.existsSync(pythonPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Python是否安装了特定的库
|
||||
*/
|
||||
export async function checkPythonModule(moduleName: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const pythonPath = getPythonPath();
|
||||
|
||||
const process = spawn(pythonPath, ['-m', 'pip', 'show', moduleName], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
|
||||
process.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
process.on('close', (code) => {
|
||||
resolve(code === 0 && stdout.includes(`Name: ${moduleName}`));
|
||||
});
|
||||
|
||||
process.on('error', () => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
process.kill();
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Python版本信息
|
||||
*/
|
||||
export async function getPythonVersion(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
const pythonPath = getPythonPath();
|
||||
|
||||
const process = spawn(pythonPath, ['--version'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
process.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
process.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
process.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve((stdout || stderr).trim());
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('error', () => {
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
process.kill();
|
||||
resolve(null);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存(用于测试)
|
||||
*/
|
||||
export function clearPythonPathCache(): void {
|
||||
cachedPythonPath = null;
|
||||
}
|
||||
Reference in New Issue
Block a user