build.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """构建脚本 - 用于打包exe"""
  2. import os
  3. import sys
  4. import subprocess
  5. import shutil
  6. from pathlib import Path
  7. import glob
  8. def run_command(cmd, cwd=None):
  9. """运行命令"""
  10. print(f"执行命令: {cmd}")
  11. result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True)
  12. if result.returncode != 0:
  13. print(f"命令执行失败: {result.stderr}")
  14. return False
  15. print(result.stdout)
  16. return True
  17. def clean_build_files():
  18. """清理构建相关的文件和文件夹"""
  19. print("=" * 50)
  20. print("清理构建文件...")
  21. print("=" * 50)
  22. # 要清理的文件夹列表
  23. folders_to_clean = [
  24. "dist", # PyInstaller输出目录
  25. "build", # PyInstaller构建缓存
  26. "frontend/build", # React构建输出
  27. "backend/static", # 后端静态文件(前端构建产物)
  28. ]
  29. # 要清理的文件模式
  30. files_to_clean = [
  31. "*.spec", # PyInstaller spec文件
  32. "requirements_build.txt", # 临时requirements文件
  33. ]
  34. # 清理文件夹
  35. for folder in folders_to_clean:
  36. folder_path = Path(folder)
  37. if folder_path.exists():
  38. print(f"删除文件夹: {folder}")
  39. try:
  40. shutil.rmtree(folder_path)
  41. print(f"[OK] 已删除 {folder}")
  42. except Exception as e:
  43. print(f"[FAIL] 删除 {folder} 失败: {e}")
  44. else:
  45. print(f"- 文件夹不存在: {folder}")
  46. # 清理文件
  47. for file_pattern in files_to_clean:
  48. for file_path in glob.glob(file_pattern):
  49. try:
  50. os.remove(file_path)
  51. print(f"[OK] 已删除文件: {file_path}")
  52. except Exception as e:
  53. print(f"[FAIL] 删除文件 {file_path} 失败: {e}")
  54. # 清理Python缓存文件
  55. print("清理Python缓存文件...")
  56. for root, dirs, files in os.walk("."):
  57. # 删除__pycache__文件夹
  58. if "__pycache__" in dirs:
  59. pycache_path = Path(root) / "__pycache__"
  60. try:
  61. shutil.rmtree(pycache_path)
  62. print(f"[OK] 已删除: {pycache_path}")
  63. except Exception as e:
  64. print(f"[FAIL] 删除 {pycache_path} 失败: {e}")
  65. dirs.remove("__pycache__") # 避免继续遍历已删除的目录
  66. # 删除.pyc文件
  67. for file in files:
  68. if file.endswith(".pyc"):
  69. pyc_path = Path(root) / file
  70. try:
  71. pyc_path.unlink()
  72. print(f"[OK] 已删除: {pyc_path}")
  73. except Exception as e:
  74. print(f"[FAIL] 删除 {pyc_path} 失败: {e}")
  75. # 清理node_modules中的构建缓存(如果存在)
  76. node_modules_cache = Path("frontend/node_modules/.cache")
  77. if node_modules_cache.exists():
  78. try:
  79. shutil.rmtree(node_modules_cache)
  80. print(f"[OK] 已删除Node.js缓存: {node_modules_cache}")
  81. except Exception as e:
  82. print(f"[FAIL] 删除Node.js缓存失败: {e}")
  83. print("文件清理完成!")
  84. return True
  85. def build_frontend():
  86. """构建前端"""
  87. print("=" * 50)
  88. print("构建前端...")
  89. print("=" * 50)
  90. frontend_dir = Path("frontend")
  91. if not frontend_dir.exists():
  92. print("前端目录不存在")
  93. return False
  94. # 安装依赖
  95. if not run_command("npm install", cwd=frontend_dir):
  96. print("安装前端依赖失败")
  97. return False
  98. # 构建前端
  99. if not run_command("npm run build", cwd=frontend_dir):
  100. print("构建前端失败")
  101. return False
  102. # 复制构建文件到后端静态目录
  103. build_dir = frontend_dir / "build"
  104. static_dir = Path("backend") / "static"
  105. if static_dir.exists():
  106. shutil.rmtree(static_dir)
  107. shutil.copytree(build_dir, static_dir)
  108. print("前端构建文件已复制到后端静态目录")
  109. return True
  110. def build_exe():
  111. """构建exe文件"""
  112. print("=" * 50)
  113. print("构建exe文件...")
  114. print("=" * 50)
  115. # 安装所需依赖
  116. print("安装构建依赖...")
  117. if not run_command(f"{sys.executable} -m pip install pyinstaller"):
  118. print("安装PyInstaller失败")
  119. return False
  120. # 安装应用依赖
  121. if not run_command(f"{sys.executable} -m pip install -r backend/requirements.txt"):
  122. print("安装应用依赖失败")
  123. return False
  124. # 构建exe - 使用更详细的参数,增加进程管理相关导入
  125. pyinstaller_cmd = (
  126. 'pyinstaller --onefile --name="yibiao-simple" '
  127. '--add-data="backend;backend" '
  128. "--hidden-import=uvicorn --hidden-import=uvicorn.logging --hidden-import=uvicorn.loops "
  129. "--hidden-import=uvicorn.loops.auto --hidden-import=uvicorn.protocols "
  130. "--hidden-import=uvicorn.protocols.http --hidden-import=uvicorn.protocols.http.auto "
  131. "--hidden-import=uvicorn.protocols.websockets --hidden-import=uvicorn.protocols.websockets.auto "
  132. "--hidden-import=uvicorn.lifespan --hidden-import=uvicorn.lifespan.on --hidden-import=uvicorn.server "
  133. "--hidden-import=fastapi --hidden-import=fastapi.staticfiles --hidden-import=fastapi.responses "
  134. "--hidden-import=fastapi.middleware --hidden-import=fastapi.middleware.cors --hidden-import=fastapi.routing "
  135. "--hidden-import=fastapi.exceptions --hidden-import=starlette --hidden-import=starlette.middleware "
  136. "--hidden-import=starlette.middleware.cors --hidden-import=starlette.applications "
  137. "--hidden-import=starlette.routing --hidden-import=starlette.responses --hidden-import=starlette.staticfiles "
  138. "--hidden-import=starlette.types --hidden-import=openai --hidden-import=docx --hidden-import=docx.oxml "
  139. "--hidden-import=docx.oxml.ns --hidden-import=PyPDF2 --hidden-import=PyPDF2.generic "
  140. "--hidden-import=pdfplumber --hidden-import=pdfplumber.page --hidden-import=pdfplumber.table "
  141. "--hidden-import=pdfplumber.utils --hidden-import=fitz --hidden-import=pymupdf "
  142. "--hidden-import=docx2python --hidden-import=docx2python.iterators --hidden-import=paragraphs "
  143. "--hidden-import=pydantic --hidden-import=pydantic_settings --hidden-import=multipart "
  144. "--hidden-import=aiofiles --hidden-import=dotenv --hidden-import=json --hidden-import=pathlib "
  145. "--hidden-import=asyncio --hidden-import=signal --hidden-import=atexit "
  146. "--console app_launcher.py"
  147. )
  148. if not run_command(pyinstaller_cmd):
  149. print("构建exe失败")
  150. return False
  151. print("exe文件构建完成,位于 dist/ 目录中")
  152. return True
  153. def main():
  154. """主函数"""
  155. print("AI写标书助手 - 构建脚本")
  156. print("=" * 50)
  157. # 确保在项目根目录
  158. if not Path("backend").exists() or not Path("frontend").exists():
  159. print("请在项目根目录运行此脚本")
  160. return False
  161. # 清理构建文件
  162. if not clean_build_files():
  163. return False
  164. # 构建前端
  165. if not build_frontend():
  166. return False
  167. # 构建exe
  168. if not build_exe():
  169. return False
  170. print("\n" + "=" * 50)
  171. print("构建完成!")
  172. print("exe文件位于: dist/yibiao-simple.exe")
  173. print("=" * 50)
  174. return True
  175. if __name__ == "__main__":
  176. success = main()
  177. if not success:
  178. sys.exit(1)