setupModel.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import os
  2. import sys
  3. import re
  4. import shutil
  5. import subprocess
  6. from setuptools import setup, find_packages
  7. from setuptools.command.build_py import build_py
  8. from setuptools.command.bdist_wheel import bdist_wheel
  9. # ========== 配置 ==========
  10. PACKAGE_NAME = "dataAnalysisBehavior" # 包名(仅用于元数据)
  11. COMPILE_DIRS = ["behavior", "common"] # 需要编译的子目录(同时也是顶层包名)
  12. INSTALL_REQUIRES = [] # 根据实际依赖填写
  13. CLEAN_COMPILED_AFTER_BUILD = True
  14. # =========================
  15. def log(msg):
  16. print(f"[SETUP] {msg}")
  17. def compile_with_nuitka(py_files):
  18. for py_file in py_files:
  19. if os.path.basename(py_file) == "__init__.py":
  20. continue
  21. log(f"编译: {py_file}")
  22. output_dir = os.path.dirname(py_file)
  23. cmd = [
  24. sys.executable, "-m", "nuitka",
  25. "--module",
  26. "--no-pyi-file",
  27. "--remove-output",
  28. f"--output-dir={output_dir}",
  29. py_file
  30. ]
  31. if sys.platform == "win32":
  32. cmd.append("--mingw64")
  33. result = subprocess.run(cmd, capture_output=True, text=True)
  34. if result.returncode != 0:
  35. log(f"编译失败: {py_file}\n{result.stderr}")
  36. sys.exit(1)
  37. base_name = os.path.splitext(os.path.basename(py_file))[0]
  38. found = False
  39. for ext in [".pyd", ".so"]:
  40. pattern = re.escape(base_name) + r".*" + re.escape(ext)
  41. for f in os.listdir(output_dir):
  42. if re.match(pattern, f):
  43. src = os.path.join(output_dir, f)
  44. dst = os.path.join(output_dir, base_name + ext)
  45. shutil.move(src, dst)
  46. log(f"重命名: {src} -> {dst}")
  47. found = True
  48. break
  49. if found:
  50. break
  51. if not found:
  52. log(f"错误:未找到编译产物 {py_file}")
  53. sys.exit(1)
  54. def clean_build_dir():
  55. dirs_to_clean = ["build", f"{PACKAGE_NAME}.egg-info"]
  56. for d in dirs_to_clean:
  57. if os.path.exists(d):
  58. shutil.rmtree(d)
  59. log(f"已删除: {d}")
  60. class CustomBuildPy(build_py):
  61. def run(self):
  62. src_root = os.getcwd()
  63. log(f"源码根目录: {src_root}")
  64. # 1. 编译生成 .pyd/.so
  65. for compile_dir in COMPILE_DIRS:
  66. target_dir = os.path.join(src_root, compile_dir)
  67. if not os.path.isdir(target_dir):
  68. log(f"警告: 目录不存在 {target_dir}")
  69. continue
  70. all_py = []
  71. for root, _, files in os.walk(target_dir):
  72. for file in files:
  73. if file.endswith(".py"):
  74. all_py.append(os.path.join(root, file))
  75. if all_py:
  76. compile_with_nuitka(all_py)
  77. else:
  78. log(f"在 {target_dir} 中没有找到 .py 文件")
  79. # 2. 手动构建目录:直接复制到 build/lib/{compile_dir}(不创建顶层包目录)
  80. build_lib = self.build_lib
  81. if not build_lib:
  82. build_lib = os.path.join(src_root, "build", "lib")
  83. for compile_dir in COMPILE_DIRS:
  84. src_dir = os.path.join(src_root, compile_dir)
  85. if not os.path.isdir(src_dir):
  86. continue
  87. dst_dir = os.path.join(build_lib, compile_dir) # 直接作为顶级包
  88. self.mkpath(dst_dir)
  89. for root, _, files in os.walk(src_dir):
  90. rel_path = os.path.relpath(root, src_dir)
  91. target_subdir = os.path.join(dst_dir, rel_path) if rel_path != '.' else dst_dir
  92. self.mkpath(target_subdir)
  93. for file in files:
  94. src_file = os.path.join(root, file)
  95. if file == "__init__.py":
  96. # 复制 __init__.py
  97. dst_file = os.path.join(target_subdir, file)
  98. self.copy_file(src_file, dst_file)
  99. log(f"复制 {src_file} -> {dst_file}")
  100. elif file.endswith(".pyd") or file.endswith(".so"):
  101. # 复制编译好的二进制文件
  102. dst_file = os.path.join(target_subdir, file)
  103. self.copy_file(src_file, dst_file)
  104. log(f"复制 {src_file} -> {dst_file}")
  105. # 其他 .py 文件忽略不复制
  106. # 3. 可选:清理源码目录中的 .pyd/.so
  107. if CLEAN_COMPILED_AFTER_BUILD:
  108. for compile_dir in COMPILE_DIRS:
  109. target_dir = os.path.join(src_root, compile_dir)
  110. if not os.path.isdir(target_dir):
  111. continue
  112. for root, _, files in os.walk(target_dir):
  113. for file in files:
  114. if file.endswith(".pyd") or file.endswith(".so"):
  115. os.remove(os.path.join(root, file))
  116. log(f"清理源码编译产物: {os.path.join(root, file)}")
  117. class CustomBdistWheel(bdist_wheel):
  118. def run(self):
  119. self.run_command("build_py")
  120. self.plat_name = self.get_platform()
  121. bdist_wheel.run(self)
  122. def get_platform(self):
  123. if sys.platform == "win32":
  124. return "win_amd64"
  125. elif sys.platform == "linux":
  126. return "linux_x86_64"
  127. else:
  128. return super().get_platform()
  129. if __name__ == "__main__":
  130. try:
  131. subprocess.run([sys.executable, "-m", "nuitka", "--version"], capture_output=True, check=True)
  132. except subprocess.CalledProcessError:
  133. log("请先安装 Nuitka: pip install nuitka", "ERROR")
  134. sys.exit(1)
  135. clean_build_dir()
  136. setup(
  137. name=PACKAGE_NAME,
  138. version="1.2.202606101701",
  139. description="Data Analysis Behavior Package (compiled, no source in wheel)",
  140. author="Xie Zhou Yang",
  141. packages=["behavior", "common"], # 直接指定顶层包
  142. package_dir={"": "."}, # 包目录在当前目录
  143. cmdclass={
  144. "build_py": CustomBuildPy,
  145. "bdist_wheel": CustomBdistWheel,
  146. },
  147. install_requires=INSTALL_REQUIRES,
  148. zip_safe=False,
  149. )