add build to pyd

This commit is contained in:
bluepulsar
2026-01-04 17:30:22 +08:00
parent 07efe0ff81
commit 3d805c69db
5 changed files with 911 additions and 5 deletions
+1
View File
@@ -5,3 +5,4 @@
__pycache__/ __pycache__/
orisys.egg-info/ orisys.egg-info/
/.cursor/ /.cursor/
/dist_pyd/
+902
View File
@@ -0,0 +1,902 @@
"""
Build script to create wheel distributions with .pyd/.so files instead of .py files.
This script uses Cython to compile Python source files into extension modules (.pyd on Windows, .so on Linux),
making the source code completely hidden in the final distribution.
Requirements:
- Cython
- A C compiler (Visual Studio Build Tools on Windows, GCC on Linux)
"""
import os
import shutil
import subprocess
import sys
import platform
from pathlib import Path
import tempfile
# Package version
VERSION = "0.2.0"
# Python versions to build for
PYTHON_VERSIONS = ['3.8', '3.9', '3.10']
# Detect platform
IS_WINDOWS = platform.system() == 'Windows'
IS_LINUX = platform.system() == 'Linux'
PLATFORM_NAME = 'win' if IS_WINDOWS else 'linux' if IS_LINUX else 'unknown'
# Extension for compiled modules
EXT_EXTENSION = '.pyd' if IS_WINDOWS else '.so'
def clean_build_dirs():
"""Remove previous build artifacts."""
dirs_to_clean = ['build', 'dist', 'orisys.egg-info', 'dist_pyd', 'build_pyd']
for dir_name in dirs_to_clean:
if os.path.exists(dir_name):
print(f"Cleaning {dir_name}...")
try:
# Try to remove with error handling
shutil.rmtree(dir_name, ignore_errors=False, onerror=_handle_remove_error)
except (PermissionError, OSError) as e:
print(f" ⚠️ Warning: Could not fully remove {dir_name}: {e}")
print(f" Some files may be locked. Continuing anyway...")
# Try again with ignore_errors=True
try:
shutil.rmtree(dir_name, ignore_errors=True)
except Exception:
pass
# Ensure dist directory exists with platform subdirectories
os.makedirs(f'dist/{PLATFORM_NAME}', exist_ok=True)
def _handle_remove_error(func, path, exc_info):
"""Handle errors during directory removal."""
# If it's a permission error, try to change permissions and retry
if func is os.rmdir:
try:
# Try to change permissions and remove
os.chmod(path, 0o777)
os.rmdir(path)
except Exception:
# If that fails, just skip it
pass
elif func is os.remove:
try:
os.chmod(path, 0o777)
os.remove(path)
except Exception:
pass
def check_cython():
"""Check if Cython is installed."""
try:
import Cython
print(f"✓ Cython {Cython.__version__} is available")
return True
except ImportError:
print("❌ Cython is not installed!")
print(" Install it with: pip install cython")
return False
def check_c_compiler():
"""Check if a C compiler is available."""
if IS_WINDOWS:
# On Windows, check for cl.exe (Microsoft Visual C++)
# Note: cl.exe might not be in PATH even if installed
# We'll do a basic check but the actual compilation will provide better errors
cl_path = shutil.which('cl.exe')
if cl_path:
print("✓ C compiler (cl.exe) found in PATH")
return True
else:
print("⚠️ C compiler (cl.exe) not found in PATH")
print(" (This is OK if Visual Studio is installed but not in PATH)")
print(" The build will attempt to use the compiler anyway...")
return True # Return True to allow build attempt - it will fail with better error if missing
else:
# On Linux/Mac, check for gcc
try:
result = subprocess.run(
['gcc', '--version'], capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
print(f"✓ C compiler (gcc) is available")
return True
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
print("❌ C compiler (gcc) not found!")
print("\n Install with:")
if IS_LINUX:
print(" sudo apt-get install build-essential # Ubuntu/Debian")
print(" sudo yum install gcc gcc-c++ # CentOS/RHEL")
else:
print(" Install Xcode Command Line Tools:")
print(" xcode-select --install")
return False
def get_python_executable(version):
"""Get the Python executable for a specific version based on platform."""
current_version = f"{sys.version_info.major}.{sys.version_info.minor}"
if current_version == version:
return [sys.executable]
if IS_WINDOWS:
username = os.environ.get("USERNAME", "")
possible_paths = [
f'py -{version}',
f'python{version}',
f'C:\\Users\\{username}\\anaconda3\\envs\\py{version.replace(".", "")}\\python.exe',
f'C:\\Users\\{username}\\anaconda3\\envs\\py{version.replace(".", "")}-env\\python.exe',
f'C:\\Users\\{username}\\anaconda3\\envs\\python{version.replace(".", "")}\\python.exe',
f'C:\\Users\\{username}\\anaconda3\\envs\\orisys-py{version.replace(".", "")}\\python.exe',
f'C:\\Users\\{username}\\miniconda3\\envs\\py{version.replace(".", "")}\\python.exe',
f'C:\\Python{version.replace(".", "")}\\python.exe',
f'C:\\Program Files\\Python{version.replace(".", "")}\\python.exe',
]
else:
possible_paths = [
f'python{version}',
f'/usr/bin/python{version}',
f'/usr/local/bin/python{version}',
f'~/.pyenv/versions/{version}/bin/python',
f'/opt/python{version}/bin/python',
]
for path in possible_paths:
try:
if path.startswith('~'):
path = os.path.expanduser(path)
if IS_WINDOWS and path.startswith('py -'):
result = subprocess.run(
path.split() + ['-c', 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'],
capture_output=True, text=True, shell=True
)
if result.returncode == 0 and result.stdout.strip() == version:
return path.split()
else:
if not os.path.exists(path):
continue
result = subprocess.run(
[path, '-c', 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'],
capture_output=True, text=True
)
if result.returncode == 0 and result.stdout.strip() == version:
return [path]
except (FileNotFoundError, subprocess.SubprocessError, PermissionError):
continue
return None
def create_cython_setup_files():
"""Create setup.py files for Cython compilation."""
print("\n=== Creating Cython setup files ===")
# Find all Python files to compile (excluding __init__.py and configs)
src_dir = Path('src/orisys')
py_files = []
for py_file in src_dir.rglob('*.py'):
# Skip __init__.py files (they need to stay as .py for package structure)
if py_file.name == '__init__.py':
continue
# Skip getDensity.py (excluded from compilation)
if py_file.name == 'getDensity.py':
continue
# Skip configs directory (JSON files only)
if 'configs' in py_file.parts and py_file.name != '__init__.py':
continue
# Get relative path from src/orisys
rel_path = py_file.relative_to(src_dir)
py_files.append(rel_path)
print(f"Found {len(py_files)} Python files to compile")
# Create dist_pyd directory structure
dist_dir = Path('dist_pyd/src/orisys')
dist_dir.mkdir(parents=True, exist_ok=True)
# Copy ALL Python source files to dist_pyd (needed for Cython compilation)
# Cython needs the source files to compile them
print("Copying Python source files for compilation...")
for py_file in src_dir.rglob('*.py'):
rel_path = py_file.relative_to(src_dir)
# dst_file should be in dist_pyd/src/orisys/ (not dist_pyd/src/)
dst_file = dist_dir / rel_path
dst_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(py_file, dst_file)
if py_file.name == '__init__.py':
print(f" Copied: {rel_path} (will remain as .py)")
else:
print(f" Copied: {rel_path} (will be compiled to .pyd/.so)")
# Copy configs directory (JSON files)
configs_src = src_dir / 'configs'
configs_dst = dist_dir / 'configs'
if configs_src.exists():
shutil.copytree(configs_src, configs_dst, dirs_exist_ok=True)
print(f" Copied: configs/ directory")
# Create setup.py for Cython compilation
# Build the py_files list as a string
py_files_str = '[\n'
for py_file in py_files:
# Convert path to module name (e.g., "sensor.py" -> "orisys.sensor")
module_path = str(py_file).replace('\\', '/').replace('.py', '').replace('/', '.')
file_path = f"src/orisys/{py_file}"
py_files_str += f' ("orisys.{module_path}", "{file_path}"),\n'
py_files_str += ']'
# Build setup.py content using string concatenation to avoid f-string escaping issues
# Note: We need to handle Windows multiprocessing properly
# The if __name__ == '__main__': guard is required for Windows multiprocessing support
setup_content = '''from setuptools import setup, Extension
from Cython.Build import cythonize
from pathlib import Path
import numpy
import sys
import platform
import multiprocessing
import os
# Package version
VERSION = "''' + VERSION + '''"
# Get include directories
numpy_include = numpy.get_include()
# On Windows, find Python library for explicit linking if needed
if platform.system() == 'Windows':
import os
python_libs_dir = os.path.join(sys.base_prefix, 'libs')
python_version = f"{sys.version_info.major}{sys.version_info.minor}"
python_lib_name = f"python{python_version}"
# Check if library file exists
python_lib_path = os.path.join(python_libs_dir, f"{python_lib_name}.lib")
has_python_lib = os.path.exists(python_lib_path) if python_libs_dir and os.path.exists(python_libs_dir) else False
else:
python_libs_dir = None
python_lib_name = None
python_lib_path = None
has_python_lib = False
# List of Python files to compile (excluding __init__.py)
py_files = ''' + py_files_str + '''
# Create Extension objects
extensions = []
for module_name, file_path in py_files:
# Setup library directories and libraries for Windows
library_dirs = []
libraries = []
extra_link_args = []
if platform.system() == 'Windows' and has_python_lib:
# Explicitly link Python library on Windows
library_dirs = [python_libs_dir]
# Use libraries parameter to link Python library
# This is the standard way setuptools expects
libraries = [python_lib_name]
ext = Extension(
module_name,
[file_path],
include_dirs=[numpy_include],
library_dirs=library_dirs,
libraries=libraries,
# Add any additional compiler flags here
extra_compile_args=[],
extra_link_args=extra_link_args,
)
extensions.append(ext)
# Determine number of threads for parallel compilation
# On Windows, disable parallel compilation to avoid multiprocessing spawn issues
# On Linux/Mac, use parallel compilation for faster builds
if platform.system() == 'Windows':
nthreads = 1 # Single-threaded on Windows to avoid multiprocessing issues
else:
nthreads = 4 # Parallel compilation on Linux/Mac
# Windows multiprocessing requires this guard
if __name__ == '__main__':
# Required for Windows multiprocessing support
multiprocessing.freeze_support()
# On Windows, ensure 64-bit build if Python is 64-bit
if platform.system() == 'Windows':
import sysconfig
# Check if Python is 64-bit
if sys.maxsize > 2**32:
# Force 64-bit build by setting distutils environment
import distutils.util
# This should force 64-bit build
try:
# Try to set the platform to win-amd64
import distutils._msvccompiler
# The compiler should automatically detect 64-bit Python
pass
except:
pass
setup(
name="orisys",
version=VERSION,
description="Orisys - Optical Tactile Sensor SDK (Compiled)",
author="Your Name",
ext_modules=cythonize(
extensions,
compiler_directives={
'language_level': "3",
'embedsignature': True,
# Optimize for size and speed
'optimize.use_switch': True,
'optimize.unpack_method_calls': True,
},
build_dir="build_pyd",
# Create .pyd/.so files instead of .py
nthreads=nthreads, # Parallel compilation (disabled on Windows)
),
zip_safe=False,
include_package_data=True,
package_data={
"orisys": ["configs/*.json"],
},
)
'''
setup_file = Path('dist_pyd/setup.py')
with open(setup_file, 'w') as f:
f.write(setup_content)
print(f"Created setup.py with {len(py_files)} modules to compile")
return py_files
def compile_with_cython(python_version):
"""Compile Python files to .pyd/.so using Cython."""
print(f"\n{'='*60}")
print(f"Compiling with Cython for Python {python_version} on {PLATFORM_NAME}")
print('='*60)
python_cmd = get_python_executable(python_version)
if not python_cmd:
print(f"⚠️ Python {python_version} not found on {PLATFORM_NAME}, skipping...")
return False
print(f"Using Python: {' '.join(python_cmd)}")
# Verify Python version
result = subprocess.run(
python_cmd + ['-c', 'import sys; print(f"Python {sys.version}")'],
capture_output=True, text=True
)
print(result.stdout.strip())
# Check if Cython is installed in this Python
result = subprocess.run(
python_cmd + ['-c', 'import Cython; print(f"Cython {Cython.__version__}")'],
capture_output=True, text=True
)
if result.returncode != 0:
print(f"❌ Cython not installed for Python {python_version}")
print(f" Install with: {' '.join(python_cmd)} -m pip install cython")
return False
print(result.stdout.strip())
# Check if numpy is installed (required for compilation)
result = subprocess.run(
python_cmd + ['-c', 'import numpy; print(f"NumPy {numpy.__version__}")'],
capture_output=True, text=True
)
if result.returncode != 0:
print(f"❌ NumPy not installed for Python {python_version}")
print(f" Install with: {' '.join(python_cmd)} -m pip install numpy")
return False
print(result.stdout.strip())
os.chdir('dist_pyd')
# On Windows, ensure we use the correct architecture (64-bit)
# Check Python architecture first
if IS_WINDOWS:
arch_check = subprocess.run(
python_cmd + ['-c', 'import sys; print("64bit" if sys.maxsize > 2**32 else "32bit")'],
capture_output=True, text=True
)
if arch_check.returncode == 0:
python_arch = arch_check.stdout.strip()
print(f"Python architecture: {python_arch}")
if python_arch == "64bit":
# Force 64-bit build on Windows by setting environment variable
# This tells distutils to use the 64-bit toolchain
os.environ['DISTUTILS_USE_SDK'] = '1'
# Also try to find and use vcvars64.bat if available
# But setuptools should handle this automatically
print(" Using 64-bit build configuration")
# Build extensions
# Don't use --inplace to avoid directory issues
# Build to build/lib directory, then copy to src/orisys
cmd = python_cmd + ['setup.py', 'build_ext']
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
os.chdir('..')
if result.returncode != 0:
print(f"❌ Error compiling with Cython")
# Check for common compiler errors
error_output = result.stderr + "\n" + result.stdout
if "cl.exe" in error_output and "failed" in error_output:
print("\n ⚠️ C compiler (cl.exe) error detected!")
print(" This usually means:")
print(" 1. Visual Studio Build Tools are not installed")
print(" 2. The compiler is not in your PATH")
print(" 3. The Visual Studio environment is not set up")
print("\n Solutions:")
print(" - Install Visual Studio Build Tools with C++ support")
print(" - Or open 'Developer Command Prompt for VS' and run from there")
print(" - Or run: vcvars64.bat before building")
print(f"\n Full error output:\n{result.stderr}")
if result.stdout and result.stderr != result.stdout:
print(f"\n stdout:\n{result.stdout}")
return False
print(f"✅ Compilation successful for Python {python_version}!")
# Copy compiled files from build directory to src/orisys
build_lib_dir = Path('build')
target_dir = Path('src/orisys')
# Find the build directory (it may be build/lib.win-amd64-cpython-310 or similar)
compiled_files = []
if build_lib_dir.exists():
# Find the actual build output directory
for lib_dir in build_lib_dir.glob('lib.*'):
orisys_build_dir = lib_dir / 'orisys'
if orisys_build_dir.exists():
# Copy all .pyd/.so files to src/orisys
print(f" Copying compiled files from {orisys_build_dir} to {target_dir}...")
target_dir.mkdir(parents=True, exist_ok=True)
for compiled_file in orisys_build_dir.glob(f'*{EXT_EXTENSION}'):
# Copy with original filename (e.g., sensor.cp310-win_amd64.pyd)
target_file = target_dir / compiled_file.name
shutil.copy(compiled_file, target_file)
compiled_files.append(target_file)
# Also create a symlink/copy with base name (e.g., sensor.pyd)
# Python can import from either name, but base name is more reliable
base_name = compiled_file.stem.split('.')[0] # Get 'sensor' from 'sensor.cp310-win_amd64'
base_file = target_dir / f"{base_name}{EXT_EXTENSION}"
if not base_file.exists():
shutil.copy(compiled_file, base_file)
compiled_files.append(base_file)
print(f" Copied: {compiled_file.name} -> {target_file} and {base_file.name}")
else:
print(f" Copied: {compiled_file.name} -> {target_file}")
break
# Verify files are in the target location
if not compiled_files:
compiled_files = list(target_dir.rglob(f'*{EXT_EXTENSION}'))
if compiled_files:
print(f" Found {len(compiled_files)} existing {EXT_EXTENSION} files in target directory")
print(f" Created {len(compiled_files)} {EXT_EXTENSION} files")
# List some example files
if compiled_files:
print(f" Examples: {', '.join([f.name for f in compiled_files[:3]])}")
if len(compiled_files) > 3:
print(f" ... and {len(compiled_files) - 3} more")
else:
print(" ⚠️ Warning: No compiled files found in expected locations")
print(" This may indicate a problem with the build process")
# Remove original .py files (except __init__.py) after successful compilation
# This ensures only compiled .pyd/.so files remain
print("\n=== Removing original .py source files (keeping only compiled extensions) ===")
removed_count = 0
for py_file in Path('dist_pyd/src/orisys').rglob('*.py'):
# Keep __init__.py files
if py_file.name == '__init__.py':
continue
# Check if corresponding .pyd/.so file exists
compiled_file = py_file.with_suffix(EXT_EXTENSION)
if compiled_file.exists():
try:
py_file.unlink()
removed_count += 1
print(f" Removed: {py_file.relative_to('dist_pyd')}")
except Exception as e:
print(f" Warning: Could not remove {py_file.name}: {e}")
if removed_count > 0:
print(f"Removed {removed_count} original .py files (compiled versions exist)")
else:
print("No .py files removed (compiled files may not have been created)")
# Return True if compilation was successful (returncode == 0)
# Even if file verification had issues, compilation itself succeeded
return True
def create_package_setup(python_version=None):
"""Create setup.py for packaging the compiled .pyd/.so files."""
print(f"\n=== Creating package setup.py (Python {python_version or '3.8-3.10'}) ===")
if python_version:
version_constraint = f'=={python_version}.*'
py_tag = f'py{python_version.replace(".", "")}'
else:
version_constraint = '>=3.8,<3.11'
py_tag = 'py38.py39.py310'
# Before packaging, ensure .pyd files are in src/orisys
# They might still be in build directory, so copy them if needed
print("=== Ensuring .pyd files are in src/orisys before packaging ===")
orisys_dir = Path('dist_pyd/src/orisys')
build_dir = Path('dist_pyd/build')
# Check if files are in build directory and copy them
copied_count = 0
if build_dir.exists():
for lib_dir in build_dir.glob('lib.*'):
orisys_build_dir = lib_dir / 'orisys'
if orisys_build_dir.exists():
print(f" Found .pyd files in {orisys_build_dir}, copying to src/orisys...")
orisys_dir.mkdir(parents=True, exist_ok=True)
for compiled_file in orisys_build_dir.glob(f'*{EXT_EXTENSION}'):
target_file = orisys_dir / compiled_file.name
# Always copy to ensure we have the latest version
shutil.copy(compiled_file, target_file)
copied_count += 1
print(f" Copied: {compiled_file.name} -> {target_file}")
if copied_count > 0:
print(f" Successfully copied {copied_count} .pyd files to src/orisys")
else:
print(" ⚠️ No .pyd files found in build directory to copy")
# Before packaging, ensure all .py files (except __init__.py) are removed
print("=== Final cleanup: Removing any remaining .py source files ===")
removed_count = 0
if orisys_dir.exists():
for py_file in orisys_dir.rglob('*.py'):
# Keep __init__.py files
if py_file.name == '__init__.py':
continue
# Check if corresponding .pyd/.so file exists
compiled_file = py_file.with_suffix(EXT_EXTENSION)
# Check for any .pyd file with this base name
base_name = py_file.stem
has_compiled = False
if compiled_file.exists():
has_compiled = True
else:
# Check for versioned .pyd files (e.g., sensor.cp310-win_amd64.pyd)
for pyd_file in orisys_dir.glob(f'{base_name}.*{EXT_EXTENSION}'):
has_compiled = True
break
if has_compiled:
try:
py_file.unlink()
removed_count += 1
print(f" Removed: {py_file.relative_to('dist_pyd')}")
except Exception as e:
print(f" Warning: Could not remove {py_file.name}: {e}")
else:
# If no compiled version exists, this is a problem
print(f" ⚠️ Warning: {py_file.name} has no compiled version, removing anyway")
try:
py_file.unlink()
removed_count += 1
except Exception as e:
print(f" Warning: Could not remove {py_file.name}: {e}")
if removed_count > 0:
print(f"Removed {removed_count} .py files before packaging")
# Find all compiled .pyd/.so files in src/orisys
compiled_files = list(orisys_dir.rglob(f'*{EXT_EXTENSION}')) if orisys_dir.exists() else []
print(f"Found {len(compiled_files)} compiled {EXT_EXTENSION} files in src/orisys")
if len(compiled_files) == 0:
print(" ⚠️ WARNING: No .pyd files found in src/orisys!")
print(" This will cause the wheel to be missing compiled modules.")
# Create MANIFEST.in to ensure .pyd files are included
manifest_content = f"""# Include all compiled extensions
include src/orisys/*{EXT_EXTENSION}
recursive-include src/orisys *{EXT_EXTENSION}
# Include JSON config files
recursive-include src/orisys/configs *.json
# Exclude all .py files except __init__.py
global-exclude *.py
include src/orisys/__init__.py
recursive-include src/orisys/configs __init__.py
"""
manifest_file = Path('dist_pyd/MANIFEST.in')
with open(manifest_file, 'w') as f:
f.write(manifest_content)
print("Created MANIFEST.in to ensure .pyd files are included")
setup_content = f'''from setuptools import setup, find_packages
from setuptools.command.build_py import build_py as _build_py
from pathlib import Path
import os
import shutil
import glob
# Read requirements
with open('requirements.txt', 'r') as f:
requirements = [line.strip() for line in f if line.strip() and not line.startswith('#')]
# Find all packages
packages = find_packages(where="src")
# Collect package data (JSON files and .pyd/.so files)
# IMPORTANT: .pyd/.so files with version tags (e.g., sensor.cp310-win_amd64.pyd)
# are NOT automatically included by setuptools, so we must explicitly list them
package_data = {{}}
for pkg in packages:
pkg_path = Path('src') / pkg.replace('.', '/')
if pkg_path.exists():
data_files = []
# Explicitly include compiled extensions (.pyd/.so) using glob pattern
# This ensures all .pyd files are included regardless of version tags
for pattern in ['*{EXT_EXTENSION}']:
for ext_file in pkg_path.glob(pattern):
rel_path = ext_file.relative_to(pkg_path)
data_files.append(str(rel_path))
# Also check subdirectories
for ext_file in pkg_path.rglob(pattern):
rel_path = ext_file.relative_to(pkg_path)
if str(rel_path) not in data_files:
data_files.append(str(rel_path))
# Include JSON files from configs directory
if pkg == 'orisys.configs':
for json_file in pkg_path.glob('*.json'):
rel_path = json_file.relative_to(pkg_path)
data_files.append(str(rel_path))
# For orisys package, include configs subdirectory JSON files
if pkg == 'orisys':
configs_dir = pkg_path / 'configs'
if configs_dir.exists() and configs_dir.is_dir():
for json_file in configs_dir.glob('*.json'):
rel_path = json_file.relative_to(pkg_path)
data_files.append(str(rel_path))
if data_files:
package_data[pkg] = data_files
print(f"Package {{pkg}} package_data: {{len(data_files)}} files")
# Custom build_py to ensure .pyd files are copied
class build_py(_build_py):
def run(self):
"""Override to ensure .pyd files are included"""
super().run()
# After building, verify .pyd files are in the build directory
for pkg in packages:
pkg_path = Path('src') / pkg.replace('.', '/')
build_pkg_path = Path(self.build_lib) / pkg.replace('.', '/')
if pkg_path.exists() and build_pkg_path.exists():
# Copy any .pyd files that might have been missed
for ext_file in pkg_path.glob('*{EXT_EXTENSION}'):
build_ext_file = build_pkg_path / ext_file.name
if not build_ext_file.exists():
shutil.copy(ext_file, build_ext_file)
print(f" Copied {{ext_file.name}} to build directory")
setup(
name="orisys",
version="{VERSION}",
description="Orisys - Optical Tactile Sensor SDK (Compiled)",
author="Your Name",
package_dir={{"": "src"}},
packages=packages,
package_data=package_data,
include_package_data=True,
install_requires=requirements,
python_requires="{version_constraint}",
zip_safe=False,
cmdclass={{"build_py": build_py}},
options={{
'bdist_wheel': {{
'python_tag': '{py_tag}',
}}
}},
)
'''
with open('dist_pyd/setup.py', 'w') as f:
f.write(setup_content)
print("Package setup.py created successfully!")
def build_wheel_for_version(python_version):
"""Build the wheel distribution for a specific Python version."""
print(f"\n{'='*60}")
print(f"Building wheel for Python {python_version} on {PLATFORM_NAME}")
print('='*60)
python_cmd = get_python_executable(python_version)
if not python_cmd:
print(f"⚠️ Python {python_version} not found, skipping...")
return False
# Get platform tag
plat_tag = get_platform_tag()
py_tag = f'py{python_version.replace(".", "")}'
os.chdir('dist_pyd')
# Build the wheel
cmd = python_cmd + ['setup.py', 'bdist_wheel',
'--python-tag', py_tag,
'--plat-name', plat_tag,
'--dist-dir', f'../dist/{PLATFORM_NAME}']
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
os.chdir('..')
if result.returncode != 0:
print(f"❌ Error building wheel:\n{result.stderr}")
if result.stdout:
print(f"stdout:\n{result.stdout}")
return False
print(f"✅ Wheel for Python {python_version} built successfully!")
print(f" Expected: orisys-{VERSION}-{py_tag}-none-{plat_tag}.whl")
return True
def get_platform_tag():
"""Get the platform tag for wheel naming."""
system = platform.system()
machine = platform.machine().lower()
if system == 'Windows':
if machine in ('amd64', 'x86_64'):
return 'win_amd64'
elif machine in ('x86', 'i386', 'i686'):
return 'win32'
elif machine in ('arm64', 'aarch64'):
return 'win_arm64'
elif system == 'Linux':
if machine in ('x86_64', 'amd64'):
return 'manylinux2014_x86_64'
elif machine in ('aarch64', 'arm64'):
return 'manylinux2014_aarch64'
elif machine in ('i386', 'i686'):
return 'manylinux2014_i686'
elif system == 'Darwin':
if machine == 'arm64':
return 'macosx_11_0_arm64'
else:
return 'macosx_10_9_x86_64'
return 'any'
def copy_additional_files():
"""Copy non-Python files."""
print("\n=== Copying additional files ===")
# Copy requirements.txt
if os.path.exists('requirements.txt'):
shutil.copy('requirements.txt', 'dist_pyd/requirements.txt')
print("Copied requirements.txt")
# Copy README and LICENSE if they exist
for file in ['README.md', 'LICENSE']:
if os.path.exists(file):
shutil.copy(file, f'dist_pyd/{file}')
print(f"Copied {file}")
def main():
"""Main build process."""
import argparse
parser = argparse.ArgumentParser(description='Build orisys package with Cython (.pyd/.so files)')
parser.add_argument('--versions', nargs='+', default=PYTHON_VERSIONS,
help='Python versions to build for')
args = parser.parse_args()
current_py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
print("=" * 60)
print("Building Orisys Package with Cython (.pyd/.so files)")
print(f"Platform: {PLATFORM_NAME} ({platform.system()})")
print(f"Current Python: {current_py_version} ({sys.executable})")
print(f"Python Versions: {', '.join(args.versions)}")
print("=" * 60)
# Check Cython
if not check_cython():
print("\n❌ Build failed: Cython is required!")
print(" Install with: pip install cython")
sys.exit(1)
# Check C compiler
if not check_c_compiler():
print("\n❌ Build failed: C compiler is required!")
print(" Please install the required build tools (see above)")
sys.exit(1)
# Ensure we're in the right directory
script_dir = Path(__file__).parent
os.chdir(script_dir)
# Step 1: Clean previous builds
clean_build_dirs()
# Step 2: Create Cython setup files
py_files = create_cython_setup_files()
if not py_files:
print("\n❌ No Python files found to compile!")
sys.exit(1)
# Step 3: Copy additional files
copy_additional_files()
# Step 4: Compile for each Python version
success_count = 0
for version in args.versions:
# Compile with Cython
if compile_with_cython(version):
# Create package setup.py
create_package_setup(version)
# Build wheel
if build_wheel_for_version(version):
success_count += 1
if success_count == 0:
print("\n❌ Build failed - no wheels created!")
sys.exit(1)
print(f"\n✅ Successfully built {success_count}/{len(args.versions)} wheels")
print("\n" + "=" * 60)
print("✅ Build completed successfully!")
print("=" * 60)
print(f"\nPlatform: {PLATFORM_NAME}")
print(f"Compiled source: dist_pyd/")
print(f"Distribution packages: dist/{PLATFORM_NAME}/")
print("\nGenerated wheels:")
for wheel in Path(f'dist/{PLATFORM_NAME}').glob('*.whl'):
print(f" - {wheel.name}")
print("\nInstall with:")
print(f" pip install dist/{PLATFORM_NAME}/orisys-{VERSION}-*.whl")
print("\nNote: The wheel contains .pyd/.so files instead of .py files!")
if __name__ == '__main__':
main()
+2 -2
View File
@@ -19,10 +19,10 @@ def main():
# 步骤1:创建传感器对象 (必要步骤),config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径) # 步骤1:创建传感器对象 (必要步骤),config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径)
# 方式1: 从视频文件读取 # 方式1: 从视频文件读取
# sensor = orisys.Sensor("./s1.mp4", isstitch=True, cuda=True, verbose=True,config_name="prototype") sensor = orisys.Sensor("./s1.mp4", isstitch=True, cuda=True, verbose=True,config_name="prototype")
# 方式2: 从摄像头读取(取消注释使用) # 方式2: 从摄像头读取(取消注释使用)
sensor = orisys.Sensor(0, cuda=True, verbose=True, config_name="prototype") # sensor = orisys.Sensor(0, cuda=True, verbose=True, config_name="prototype")
print("\n按 'q' 退出程序,按 'r' 重置光流追踪器") print("\n按 'q' 退出程序,按 'r' 重置光流追踪器")
+2
View File
@@ -284,6 +284,8 @@ class UnstructuredGrid(object):
self.nsimplices = self.simplices.shape[0] self.nsimplices = self.simplices.shape[0]
if self.dim != self.simplices.shape[1]-1: if self.dim != self.simplices.shape[1]-1:
pdim = self.dim
sdim = self.simplices.shape[1]-1
raise SyntaxError("Dimension mismatch! pdim = "+str(pdim)+" and sdim = "+str(sdim)+" do not match!") raise SyntaxError("Dimension mismatch! pdim = "+str(pdim)+" and sdim = "+str(sdim)+" do not match!")
self.adjacent_faces = [] self.adjacent_faces = []
+3 -2
View File
@@ -296,7 +296,7 @@ class PoissonSolver(object):
def solve(self, f, verbose=False): def solve(self, f, verbose=False):
if not self.ready: if not self.ready:
init(verbose) self.prepare(verbose)
fshape = f.shape fshape = f.shape
@@ -506,7 +506,8 @@ class PoissonSolver(object):
print(' - GPU solver not optimized for unstructured points, using CPU') print(' - GPU solver not optimized for unstructured points, using CPU')
return [self.solve(f, verbose) for f in f_list] return [self.solve(f, verbose) for f in f_list]
return results # If we reach here, it's an unsupported case
raise ValueError(f"Unsupported point type '{self.ptype}' for GPU batch solver")
def clear_gpu_cache(self): def clear_gpu_cache(self):
"""Clear GPU cache to free GPU memory""" """Clear GPU cache to free GPU memory"""