setup.py 5.9 KB

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