389 lines
13 KiB
Python
389 lines
13 KiB
Python
"""
|
|
Build script to create PyArmor-obfuscated distributions for multiple Python versions.
|
|
|
|
This script builds separate wheels for Python 3.8, 3.9, and 3.10 for Windows and Linux.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import platform
|
|
from pathlib import Path
|
|
|
|
|
|
# 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'
|
|
|
|
|
|
def clean_build_dirs():
|
|
"""Remove previous build artifacts."""
|
|
dirs_to_clean = ['build', 'dist', 'orisys.egg-info', 'dist_obfuscated']
|
|
for dir_name in dirs_to_clean:
|
|
if os.path.exists(dir_name):
|
|
print(f"Cleaning {dir_name}...")
|
|
shutil.rmtree(dir_name)
|
|
|
|
# Ensure dist directory exists with platform subdirectories
|
|
os.makedirs(f'dist/{PLATFORM_NAME}', exist_ok=True)
|
|
|
|
|
|
def get_python_executable(version):
|
|
"""Get the Python executable for a specific version based on platform."""
|
|
# First check if current Python matches the requested version
|
|
current_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
if current_version == version:
|
|
return [sys.executable]
|
|
|
|
if IS_WINDOWS:
|
|
# Windows paths - including conda environments
|
|
username = os.environ.get("USERNAME", "")
|
|
possible_paths = [
|
|
f'py -{version}', # Windows py launcher
|
|
f'python{version}', # Direct version command
|
|
# Conda environments with various naming patterns
|
|
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',
|
|
# Standard Python installations
|
|
f'C:\\Python{version.replace(".", "")}\\python.exe',
|
|
f'C:\\Program Files\\Python{version.replace(".", "")}\\python.exe',
|
|
]
|
|
else:
|
|
# Linux/Unix paths
|
|
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:
|
|
# Expand user home directory
|
|
if path.startswith('~'):
|
|
path = os.path.expanduser(path)
|
|
|
|
if IS_WINDOWS and path.startswith('py -'):
|
|
# Test Windows py launcher
|
|
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:
|
|
# Check if file exists first
|
|
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 obfuscate_code():
|
|
"""Obfuscate the source code using PyArmor (version-independent)."""
|
|
print("\n=== Obfuscating code with PyArmor ===")
|
|
|
|
# PyArmor 9.x generates version-independent obfuscated code
|
|
# Output directly to dist_obfuscated/src (PyArmor will create orisys subdirectory)
|
|
if IS_WINDOWS:
|
|
cmd = 'pyarmor gen --output dist_obfuscated/src --recursive src/orisys'
|
|
print(f"Running: {cmd}")
|
|
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
|
else:
|
|
cmd = [
|
|
'pyarmor',
|
|
'gen',
|
|
'--output', 'dist_obfuscated/src',
|
|
'--recursive',
|
|
'src/orisys'
|
|
]
|
|
print(f"Running: {' '.join(cmd)}")
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
|
|
if result.returncode != 0:
|
|
print(f"Error obfuscating code:\n{result.stderr}")
|
|
print(f"stdout:\n{result.stdout}")
|
|
sys.exit(1)
|
|
|
|
print("Code obfuscation successful!")
|
|
print(result.stdout)
|
|
return True
|
|
|
|
|
|
def copy_additional_files():
|
|
"""Copy non-Python files that don't need obfuscation."""
|
|
print("\n=== Copying additional files ===")
|
|
|
|
# Copy config directory
|
|
if os.path.exists('config'):
|
|
print("Copying config directory...")
|
|
shutil.copytree('config', 'dist_obfuscated/config', dirs_exist_ok=True)
|
|
|
|
# Copy README and other metadata files
|
|
files_to_copy = ['README.md', 'Readme(未完成).md', 'requirements.txt', 'LICENSE']
|
|
for file in files_to_copy:
|
|
if os.path.exists(file):
|
|
print(f"Copying {file}...")
|
|
shutil.copy(file, f'dist_obfuscated/{file}')
|
|
|
|
|
|
def create_setup_for_obfuscated(python_version=None):
|
|
"""Create a modified setup.py for the obfuscated package."""
|
|
print(f"\n=== Creating setup.py for obfuscated package (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'
|
|
|
|
setup_content = f'''from setuptools import setup, find_packages
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# 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 including pyarmor_runtime
|
|
packages = find_packages(where="src")
|
|
|
|
# Collect all package data including PyArmor runtime files
|
|
package_data = {{}}
|
|
for pkg in packages:
|
|
pkg_path = Path('src') / pkg.replace('.', '/')
|
|
if pkg_path.exists():
|
|
# Include all .pyd, .so, .dll files (PyArmor runtime)
|
|
data_files = []
|
|
for pattern in ['*.pyd', '*.so', '*.dll', '*.dylib']:
|
|
data_files.extend([str(f.relative_to(pkg_path)) for f in pkg_path.glob(pattern)])
|
|
if data_files:
|
|
package_data[pkg] = data_files
|
|
|
|
setup(
|
|
name="orisys",
|
|
version="0.1.0",
|
|
description="Orisys - Optical Tactile Sensor SDK (Protected)",
|
|
author="Your Name",
|
|
package_dir={{"": "src"}},
|
|
packages=packages,
|
|
package_data=package_data,
|
|
include_package_data=True,
|
|
install_requires=requirements,
|
|
python_requires="{version_constraint}",
|
|
# Include PyArmor runtime files
|
|
zip_safe=False,
|
|
options={{
|
|
'bdist_wheel': {{
|
|
'python_tag': '{py_tag}',
|
|
}}
|
|
}},
|
|
)
|
|
'''
|
|
|
|
with open('dist_obfuscated/setup.py', 'w') as f:
|
|
f.write(setup_content)
|
|
|
|
print("setup.py created successfully!")
|
|
|
|
|
|
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 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 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())
|
|
|
|
# Get platform tag
|
|
plat_tag = get_platform_tag()
|
|
py_tag = f'py{python_version.replace(".", "")}'
|
|
|
|
os.chdir('dist_obfuscated')
|
|
|
|
# Build the wheel using setup.py with explicit tags
|
|
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 for Python {python_version}:\n{result.stderr}")
|
|
return False
|
|
|
|
print(f"✅ Wheel for Python {python_version} on {PLATFORM_NAME} built successfully!")
|
|
print(f" Expected: orisys-0.1.0-{py_tag}-none-{plat_tag}.whl")
|
|
return True
|
|
|
|
|
|
def build_universal_wheel():
|
|
"""Build a single universal wheel compatible with all Python versions."""
|
|
print(f"\n{'='*60}")
|
|
print(f"Building Universal Wheel (Python 3.8-3.10) for {PLATFORM_NAME}")
|
|
print('='*60)
|
|
|
|
os.chdir('dist_obfuscated')
|
|
|
|
# Build the wheel
|
|
cmd = [sys.executable, '-m', 'pip', 'wheel', '.', '--no-deps', '-w', 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 universal wheel:\n{result.stderr}")
|
|
return False
|
|
|
|
print(f"✅ Universal wheel for {PLATFORM_NAME} built successfully!")
|
|
print(result.stdout)
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Main build process."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Build orisys package with PyArmor')
|
|
parser.add_argument('--mode', choices=['universal', 'multi-version', 'current'], default='current',
|
|
help='Build mode: universal (one wheel for all), multi-version (separate wheels), current (use current Python only)')
|
|
parser.add_argument('--versions', nargs='+', default=PYTHON_VERSIONS,
|
|
help='Python versions to build for (only for multi-version mode)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Get current Python version
|
|
current_py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
|
|
print("=" * 60)
|
|
print("Building Orisys Package with PyArmor Obfuscation")
|
|
print(f"Platform: {PLATFORM_NAME} ({platform.system()})")
|
|
print(f"Current Python: {current_py_version} ({sys.executable})")
|
|
|
|
if args.mode == 'current':
|
|
print(f"Mode: Building for current Python version ({current_py_version}) only")
|
|
args.versions = [current_py_version]
|
|
# Use multi-version logic but with current version only
|
|
build_mode = 'multi-version'
|
|
else:
|
|
print(f"Python Versions: {', '.join(args.versions)}")
|
|
print(f"Mode: {args.mode}")
|
|
build_mode = args.mode
|
|
|
|
print("=" * 60)
|
|
|
|
# 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: Obfuscate the code (once, version-independent)
|
|
if not obfuscate_code():
|
|
print("\n❌ Build failed during obfuscation!")
|
|
sys.exit(1)
|
|
|
|
# Step 3: Copy additional files
|
|
copy_additional_files()
|
|
|
|
# Step 4 & 5: Build based on mode
|
|
if build_mode == 'universal':
|
|
# Build one wheel for all Python versions
|
|
create_setup_for_obfuscated()
|
|
if not build_universal_wheel():
|
|
print("\n❌ Build failed!")
|
|
sys.exit(1)
|
|
else:
|
|
# Build separate wheels for each Python version (or just current)
|
|
success_count = 0
|
|
for version in args.versions:
|
|
create_setup_for_obfuscated(version)
|
|
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"Obfuscated source: dist_obfuscated/")
|
|
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-0.1.0-*.whl")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|