插件编译

本文档介绍 Python 后端的编译方式。其他语言(Node.js、exe 等)请自行编译。

一、安装编译工具

pip install --upgrade Nuitka==2.6.6 -i https://pypi.doubanio.com/simple/

二、编译命令

在 cmd 中打开插件后端目录,执行编译命令:

cd plugins/my-plugin/backend
nuitka --include-module=utils --output-dir=compile --module handlers.py

编译完成后,compile/ 目录下会生成 handlers.cp310-win_amd64.pyd 文件。


三、前端编译

进入插件前端目录,执行打包命令:

cd plugins/my-plugin/frontend
pnpm build

编译完成后,dist/ 目录下会生成 index.html 文件。


四、编译脚本(可选)

也可以使用项目根目录的 compile.py 脚本批量编译。

4.1 脚本代码

import os.path
import sys
import subprocess
import time
#下面是举的例子,具体编译需要把 插件名改为自己的插件名字。要编译其他包自己调整编译命令
compile_list = {
    "textwork":["nuitka   --include-module=utils   --output-dir=compile --module handlers.py"],
    "demucx":["nuitka   --include-module=utils   --output-dir=compile --module handlers.py","nuitka   --include-module=uvr5   --output-dir=compile --module hand_worker.py"]
}


def get_backend_dir_from_manifest(plugin_dir):
    """
    从插件的 manifest.json 读取 backend.entry,返回后端文件所在目录
    如果 dev_mode 为 true,则优先读取 dev.backend.entry
    """
    import json
    manifest_path = os.path.join(plugin_dir, 'manifest.json')
    if not os.path.exists(manifest_path):
        return None

    with open(manifest_path, 'r', encoding='utf-8') as f:
        manifest = json.load(f)

    entry = None

    # 如果 dev_mode 为 true,优先读取 dev.backend.entry
    dev_mode = manifest.get('dev_mode', False)
    if dev_mode:
        dev = manifest.get('dev')
        if dev:
            dev_backend = dev.get('backend')
            if dev_backend:
                entry = dev_backend.get('entry')

    # 如果没有从 dev 获取到 entry,则从 backend 获取
    if not entry:
        backend = manifest.get('backend')
        if not backend:
            return None
        entry = backend.get('entry')

    if not entry:
        return None

    # 判断是相对路径还是绝对路径
    if os.path.isabs(entry):
        entry_path = entry
    else:
        entry_path = os.path.join(plugin_dir, entry)

    # 返回该文件所在的目录
    return os.path.dirname(os.path.abspath(entry_path))


# 分割一个列表为多个列表
def split_list(result, split_num):
    return [result[i:i + split_num] for i in range(0, len(result), split_num)]


def compile_frontend(plugin_name, plugin_dir, dubug=False):
    """
    编译插件的前端(使用 pnpm build)
    """
    frontend_dir = os.path.join(plugin_dir, 'frontend')

    # 检查 frontend 目录是否存在
    if not os.path.exists(frontend_dir):
        if dubug:
            print(f"插件 {plugin_name} 没有 frontend 目录,跳过前端编译")
        return

    # 检查是否有 package.json
    package_json = os.path.join(frontend_dir, 'package.json')
    if not os.path.exists(package_json):
        if dubug:
            print(f"插件 {plugin_name} 的 frontend 目录没有 package.json,跳过前端编译")
        return

    print(f"开始编译前端: {plugin_name}")
    t1 = time.time()

    try:
        # 执行 pnpm build,使用 utf-8 编码避免 Windows 下的 GBK 解码错误
        process = subprocess.Popen(
            'pnpm build',
            cwd=frontend_dir,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding='utf-8',
            errors='replace'  # 遇到无法解码的字符时用 ? 替换
        )

        stdout, stderr = process.communicate()
        exit_code = process.returncode

        if exit_code == 0:
            print(f"前端编译完成: {plugin_name}, 耗时: {time.time() - t1:.2f}秒")
            if dubug:
                print(f"{stdout}")
                print('-------------------------------------------------------------------------------------')
        else:
            print(f"前端编译失败: {plugin_name}, 耗时: {time.time() - t1:.2f}秒")
            print(f"标准输出:\n{stdout}")
            print(f"标准错误:\n{stderr}")
    except Exception as e:
        print(f"前端编译出错: {plugin_name}, 错误: {e}")


def run_cmds(cmd_list, dubug=False):
    processes = []
    for line in cmd_list:
        print(line)
        name, command, cwd = line
        if not os.path.exists(cwd):
            continue
        main_py = command.strip().split()[-1]
        if main_py.endswith('.py'):
            if not os.path.exists(os.path.join(cwd, main_py)):
                continue
        t1 = time.time()
        # 用引号包裹 Python 路径,避免路径中有空格导致命令失败
        python_exe = f'"{sys.executable}"'
        process = subprocess.Popen(python_exe + " -m " + command, cwd=cwd, shell=True, text=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        processes.append((process, name, t1))

    # 检查每个进程的执行状态
    for data in processes:
        process, name, t1 = data
        stdout, stderr = process.communicate()  # 等待进程完成并获取输出
        exit_code = process.returncode  # 获取返回码
        # 检查执行状态
        if exit_code == 0:
            print("编译完成", name, '耗时:', time.time() - t1)
            if dubug:
                # print(f"{stdout}")
                print(f"{stderr}")
                print('-------------------------------------------------------------------------------------')
        else:
            print("失败---", name, '耗时:', time.time() - t1)
            print(f"标准错误:\n{stderr}")


def run_compile_list(name=None, dubug=True):
    """
    使用 compile_list 数据结构进行编译
    从 plugins 目录读取 manifest.json,获取 backend.entry 的目录作为工作目录
    编译完后端后,自动编译前端(如果存在)
    """
    plugins_dir = os.path.join(os.getcwd(), 'plugins')
    cmd_list = []
    plugins_to_compile_frontend = []  # 记录需要编译前端的插件

    # 确定要编译的插件
    if name:
        if  compile_list.get(name):
            target_plugins = {name: compile_list[name]}
        else:
            exit(f'没有{name}插件')
    else:
        target_plugins = compile_list

    for plugin_name, commands in target_plugins.items():
        plugin_dir = os.path.join(plugins_dir, plugin_name)
        if not os.path.exists(plugin_dir):
            print(f"插件目录不存在: {plugin_dir}")
            continue

        # 从 manifest.json 获取后端目录
        backend_dir = get_backend_dir_from_manifest(plugin_dir)
        if not backend_dir:
            print(f"无法获取插件 {plugin_name} 的后端目录")
            continue

        if not os.path.exists(backend_dir):
            print(f"后端目录不存在: {backend_dir}")
            continue

        # 添加命令到列表
        if isinstance(commands, list):
            for cmd in commands:
                cmd_list.append([plugin_name, cmd, backend_dir])
        else:
            cmd_list.append([plugin_name, commands, backend_dir])

        # 记录需要编译前端的插件
        plugins_to_compile_frontend.append((plugin_name, plugin_dir))

    # 分批执行后端编译
    print("=" * 80)
    print("开始编译后端...")
    print("=" * 80)
    for data in split_list(cmd_list, 6):
        run_cmds(data, dubug=dubug)

    # 编译前端
    print("\n" + "=" * 80)
    print("开始编译前端...")
    print("=" * 80)
    for plugin_name, plugin_dir in plugins_to_compile_frontend:
        compile_frontend(plugin_name, plugin_dir, dubug=dubug)


if __name__ == '__main__':
    run_compile_list(dubug=True)

4.2 使用方式

编译所有插件:

python compile.py

编译单个插件,修改脚本末尾:

if __name__ == '__main__':
    run_compile_list(name="my-plugin", dubug=True)
0个回答默认排序 投票数排序
还没有回答~
请先登录