Files
TJWaterServerBinary/scripts/compile.py
T

136 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python
import os
import sys
import shutil
from setuptools import setup, Extension
from Cython.Build import cythonize
def build_extensions(target_dirs):
"""
Compiles all .py files in the target directories (recursively) into .so/.pyd extensions.
Args:
target_dirs (list or str): List of directories or single directory path to scan.
"""
# Ensure input is a list
if isinstance(target_dirs, str):
target_dirs = [target_dirs]
extensions = []
project_root = os.getcwd()
print(f"Scanning directories: {target_dirs}")
for target_dir in target_dirs:
# Ensure target directory exists
if not os.path.exists(target_dir):
print(f"Warning: Path '{target_dir}' not found. Skipping.")
continue
# Get the absolute path of the target
abs_target_path = os.path.abspath(target_dir)
# Check if the target is a file or directory
if os.path.isfile(abs_target_path):
if abs_target_path.endswith(".py"):
file_path = abs_target_path
rel_path = os.path.relpath(file_path, project_root)
module_name = os.path.splitext(rel_path)[0].replace(os.sep, ".")
print(f"Found file: {rel_path} -> {module_name}")
extensions.append(Extension(module_name, [file_path]))
continue
print(f"Scanning {abs_target_path} for Python files...")
# Walk through the directory
for root, dirs, files in os.walk(abs_target_path):
for file in files:
if file.endswith(".py"):
file_path = os.path.join(root, file)
# Skip this script if it happens to be in the target dir
if os.path.abspath(file_path) == os.path.abspath(__file__):
continue
# Skip setup.py if it exists
if file == "setup.py":
continue
# Determine the module name based on path relative to project root
# This ensures imports like 'from app.services import ...' work
try:
rel_path = os.path.relpath(file_path, project_root)
except ValueError:
# If file is not under project root, we can't easily determine module name
# relative to project root. Skip or warn.
print(
f"Skipping {file_path}: cannot determine relative path to {project_root}"
)
continue
# Convert file path to module name (e.g. app/services/foo.py -> app.services.foo)
module_name = os.path.splitext(rel_path)[0].replace(os.sep, ".")
print(f"Found: {rel_path} -> {module_name}")
extensions.append(Extension(module_name, [file_path]))
if not extensions:
print("No Python files found to compile.")
return
print(f"\nCompiling {len(extensions)} modules...")
# Build options
# compiler_directives: language_level=3 for Python 3
# force=True: force recompilation even if timestamps are up to date
try:
setup(
ext_modules=cythonize(
extensions,
compiler_directives={"language_level": "3"},
build_dir="build", # Put intermediate files in build/ directory
force=True,
),
script_args=["build_ext", "--inplace"],
)
print("\nCompilation successful!")
except Exception as e:
print(f"\nCompilation failed: {e}")
sys.exit(1)
finally:
# Cleanup build directory (intermediate C files)
if os.path.exists("build"):
print("Cleaning up build artifacts...")
try:
shutil.rmtree("build")
except OSError as e:
print(f"Warning: Failed to clean up build directory: {e}")
if __name__ == "__main__":
# Default directories to compile if none provided
DEFAULT_TARGETS = [
"app/services",
"app/native/wndb",
"app/algorithms",
"app/infra/epanet/epanet.py",
]
# Check for help flag
if len(sys.argv) > 1 and sys.argv[1] in ["--help", "-h"]:
print("Usage: python scripts/build_extensions.py [directory1] [directory2] ...")
print(
"Compiles all Python files in the given directories into C extensions (.so/.pyd)."
)
print(f"Default directories if none provided: {DEFAULT_TARGETS}")
sys.exit(0)
if len(sys.argv) > 1:
target_directories = sys.argv[1:]
else:
print(f"No directories specified. Using defaults: {DEFAULT_TARGETS}")
target_directories = DEFAULT_TARGETS
build_extensions(target_directories)