Files
chatgpt-on-wechat/desktop/build/cowagent-backend.spec
zhayujie 02bc91f4af fix(ci): track desktop backend packaging sources
The global build/ gitignore rule was silently excluding the PyInstaller
spec, desktop requirements, and build script under desktop/build/, so the
release workflow failed at "pip install -r requirements-desktop.txt" with
a missing-file error. Re-include just those source files while keeping the
build outputs (dist/, build-work/) and local venv ignored.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 20:46:14 +08:00

146 lines
4.5 KiB
Python

# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec for the CowAgent desktop backend (onedir).
Produces a self-contained `cowagent-backend` folder that the Electron app
spawns directly, so end users don't need Python installed.
onedir is chosen over onefile because the backend reads data files via paths
relative to the source tree (e.g. config-template.json, skills/, chat.html);
onedir preserves that layout, while onefile's temp-extraction would break it.
Build from the repo root:
pyinstaller desktop/build/cowagent-backend.spec --noconfirm
"""
import os
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
# Resolve the repo root from the spec's own location (desktop/build/ -> root),
# independent of the current working directory. PyInstaller exposes SPECPATH.
ROOT = os.path.abspath(os.path.join(SPECPATH, '..', '..'))
def rp(*parts):
"""Absolute path under the repo root."""
return os.path.join(ROOT, *parts)
# --- Hidden imports -------------------------------------------------------
# Channels are imported dynamically by channel_factory via string names, so
# PyInstaller's static analysis can't see them. List every channel we ship
# (Feishu is intentionally excluded — lark-oapi is dropped from the desktop
# build to save ~116MB).
hiddenimports = [
# channels (dynamic import in channel/channel_factory.py)
'channel.web.web_channel',
'channel.terminal.terminal_channel',
'channel.weixin.weixin_channel',
'channel.wechatmp.wechatmp_channel',
'channel.wechatcom.wechatcomapp_channel',
'channel.wechat_kf.wechat_kf_channel',
'channel.dingtalk.dingtalk_channel',
'channel.wecom_bot.wecom_bot_channel',
'channel.qq.qq_channel',
'channel.telegram.telegram_channel',
'channel.slack.slack_channel',
'channel.discord.discord_channel',
]
# Agent tools and model providers are imported lazily in places; collect their
# submodules so nothing is missed at runtime.
hiddenimports += collect_submodules('agent.tools')
hiddenimports += collect_submodules('models')
hiddenimports += collect_submodules('voice')
hiddenimports += collect_submodules('bridge')
# Plugin framework: WebChannel -> ChatChannel imports `from plugins import *`,
# so the framework package must be present even though desktop mode never loads
# actual plugins (it's only ~tens of KB of code).
hiddenimports += [
'plugins',
'plugins.event',
'plugins.plugin',
'plugins.plugin_manager',
]
# Third-party SDKs that use lazy/conditional imports internally.
hiddenimports += collect_submodules('dashscope')
hiddenimports += [
'tiktoken_ext',
'tiktoken_ext.openai_public',
]
# --- Data files -----------------------------------------------------------
# Runtime-read files/dirs that must travel with the executable. Paths are
# (source, dest_dir_in_bundle).
datas = [
(rp('config-template.json'), '.'),
(rp('skills'), 'skills'),
# Web console served on the backend port: ship chat.html plus its static
# assets (~1.9MB) so the browser-based console works as a debug/fallback
# entry alongside the Electron UI.
(rp('channel', 'web', 'chat.html'), 'channel/web'),
(rp('channel', 'web', 'static'), 'channel/web/static'),
]
# Some libraries (tiktoken encodings, etc.) ship data files.
datas += collect_data_files('tiktoken_ext', include_py_files=False)
# --- Excludes -------------------------------------------------------------
# Keep the bundle lean: drop Feishu's heavy SDK, plugins (disabled in desktop
# mode), tests/docs, and dev-only packages.
excludes = [
'lark_oapi', # Feishu — ~116MB, excluded from desktop build
'tests',
'pip',
'wheel',
'pytest',
'playwright', # browser tool is opt-in, not bundled
]
block_cipher = None
a = Analysis(
[rp('app.py')],
pathex=[ROOT],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='cowagent-backend',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name='cowagent-backend',
)