Files
Text-Texture-Generator-for-…/build/build_addon.py
2025-09-11 14:07:55 +03:00

263 lines
9.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Build script for Text Texture Generator addon.
Creates ZIP packages for both full and free versions.
"""
import os
import sys
import json
import shutil
import zipfile
import subprocess
from pathlib import Path
from typing import Dict, List, Optional
import tempfile
import argparse
import fnmatch
def get_project_root() -> Path:
"""Get the project root directory."""
return Path(__file__).parent.parent
def load_config() -> Dict:
"""Load build configuration."""
config_path = get_project_root() / "build" / "config.json"
with open(config_path, 'r') as f:
return json.load(f)
def load_exclusions_config() -> Dict:
"""Load file exclusions configuration."""
config_path = get_project_root() / "build" / "file_exclusions.json"
with open(config_path, 'r') as f:
return json.load(f)
def clean_directory(path: Path):
"""Clean a directory by removing all contents."""
if path.exists():
shutil.rmtree(path)
path.mkdir(parents=True, exist_ok=True)
def should_exclude_file(file_path: Path, source_dir: Path, exclude_patterns: List[str], exclude_files: List[str]) -> bool:
"""Check if a file should be excluded based on patterns and specific files."""
file_str = str(file_path)
name = file_path.name
# Get relative path from source directory for exclusion checking
try:
rel_path = file_path.relative_to(source_dir)
rel_path_str = str(rel_path)
except ValueError:
rel_path_str = str(file_path)
# Check specific file exclusions
for exclude_file in exclude_files:
# Handle directory exclusions (ending with /)
if exclude_file.endswith('/'):
exclude_dir = exclude_file.rstrip('/')
if rel_path_str.startswith(exclude_dir + '/') or rel_path_str == exclude_dir:
return True
# Handle exact file matches
elif rel_path_str == exclude_file:
return True
# Check general patterns (for cache files, etc.)
for pattern in exclude_patterns:
if pattern in file_str or pattern in name:
return True
# Handle wildcards
if pattern.startswith('*.'):
extension = pattern[1:]
if file_str.endswith(extension):
return True
# Handle glob patterns
if fnmatch.fnmatch(name, pattern) or fnmatch.fnmatch(rel_path_str, pattern):
return True
return False
def copy_source_files(source_dir: Path, dest_dir: Path, exclude_patterns: List[str], exclude_files: List[str] = None):
"""Copy source files to destination, excluding specified patterns and files."""
if exclude_files is None:
exclude_files = []
print(f"Copying files from {source_dir} to {dest_dir}")
print(f"Excluding files: {exclude_files}")
print(f"Excluding patterns: {exclude_patterns}")
copied_count = 0
excluded_count = 0
for item in source_dir.rglob('*'):
if item.is_file():
if should_exclude_file(item, source_dir, exclude_patterns, exclude_files):
rel_path = item.relative_to(source_dir)
print(f" Excluded: {rel_path}")
excluded_count += 1
else:
# Calculate relative path
rel_path = item.relative_to(source_dir)
dest_file = dest_dir / rel_path
# Create parent directories
dest_file.parent.mkdir(parents=True, exist_ok=True)
# Copy file
shutil.copy2(item, dest_file)
print(f" Copied: {rel_path}")
copied_count += 1
print(f"Files copied: {copied_count}, excluded: {excluded_count}")
def create_zip_package(source_dir: Path, output_file: Path, addon_name: str):
"""Create a ZIP package from source directory."""
print(f"Creating ZIP package: {output_file}")
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for file_path in source_dir.rglob('*'):
if file_path.is_file():
# Calculate archive path (include addon name as root folder)
rel_path = file_path.relative_to(source_dir)
archive_path = Path(addon_name) / rel_path
zip_file.write(file_path, archive_path)
print(f" Added to ZIP: {archive_path}")
print(f"ZIP package created: {output_file}")
def get_version_from_git() -> Optional[str]:
"""Get version from git tags."""
try:
result = subprocess.run(
['python3', 'build/sync_version.py', '--version'],
capture_output=True, text=True, cwd=get_project_root()
)
if result.returncode == 0:
# Extract version from output
lines = result.stdout.strip().split('\n')
for line in lines:
if 'Synchronizing version:' in line:
return line.split(':')[1].strip().split()[0]
except Exception as e:
print(f"Warning: Could not get version from git: {e}")
return None
def build_addon(version_type: str, version: Optional[str] = None, output_dir: Optional[Path] = None):
"""Build addon for specified version type."""
config = load_config()
exclusions_config = load_exclusions_config()
project_root = get_project_root()
# Determine version
if version is None:
version = get_version_from_git() or "1.0.0"
# Set up paths
source_dir = project_root / config['build']['source_dir']
if output_dir is None:
output_dir = project_root / config['build']['dist_dir']
temp_dir = project_root / config['build']['temp_dir']
# Clean and create directories
clean_directory(temp_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Sync version files for the specific version type (simplified - no feature flags)
print(f"Synchronizing version files for {version_type} version {version}")
sync_result = subprocess.run([
'python3', 'build/sync_version.py',
'--version', version,
'--type', version_type
], cwd=project_root)
if sync_result.returncode != 0:
raise RuntimeError(f"Version synchronization failed")
# Get version-specific exclusions
version_config = exclusions_config['version_configs'][version_type]
exclude_files = version_config.get('exclude_files', [])
exclude_patterns = config['build']['exclude_patterns'] + version_config.get('exclude_patterns', [])
print(f"\n📋 Build configuration for {version_type} version:")
print(f"Max texture size: {version_config.get('max_texture_size', 'unlimited')}")
print(f"Max concurrent operations: {version_config.get('max_concurrent_operations', 'unlimited')}")
if exclude_files:
print(f"Files to exclude: {exclude_files}")
if version_config.get('exclude_patterns'):
print(f"Additional patterns to exclude: {version_config.get('exclude_patterns')}")
# Copy source files to temp directory with version-specific exclusions
build_temp_dir = temp_dir / f"{config['project']['id']}_{version_type}"
copy_source_files(source_dir, build_temp_dir, exclude_patterns, exclude_files)
# Create output filename
version_suffix = f"_{version_type}" if version_type == "free" else ""
output_filename = f"{config['project']['id']}_v{version}{version_suffix}.zip"
output_file = output_dir / output_filename
# Create ZIP package
create_zip_package(build_temp_dir, output_file, config['project']['id'])
# Clean up temp directory
shutil.rmtree(temp_dir)
print(f"\n✅ Build complete!")
print(f"Package: {output_file}")
print(f"Version: {version} ({version_type})")
return output_file
def build_all_versions(version: Optional[str] = None):
"""Build both full and free versions."""
print("Building all versions...")
built_packages = []
# Build full version
print("\n" + "="*50)
print("BUILDING FULL VERSION")
print("="*50)
full_package = build_addon("full", version)
built_packages.append(("full", full_package))
# Build free version
print("\n" + "="*50)
print("BUILDING FREE VERSION")
print("="*50)
free_package = build_addon("free", version)
built_packages.append(("free", free_package))
print("\n" + "="*50)
print("BUILD SUMMARY")
print("="*50)
for version_type, package_path in built_packages:
file_size = package_path.stat().st_size / 1024 # KB
print(f"{version_type.upper():>4}: {package_path.name} ({file_size:.1f} KB)")
return built_packages
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="Build Text Texture Generator addon")
parser.add_argument("--version", help="Version to build (defaults to git tag)")
parser.add_argument("--type", choices=["free", "full", "all"], default="all",
help="Version type to build")
parser.add_argument("--output", type=Path, help="Output directory")
args = parser.parse_args()
try:
if args.type == "all":
build_all_versions(args.version)
else:
build_addon(args.type, args.version, args.output)
except Exception as e:
print(f"❌ Build failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()