263 lines
9.4 KiB
Python
Executable File
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() |