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

302 lines
11 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Package validation script for Text Texture Generator addon.
Validates ZIP packages to ensure they meet Blender addon requirements.
"""
import os
import sys
import json
import zipfile
import tempfile
from pathlib import Path
from typing import Dict, List, Tuple, Optional
import argparse
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)
class ValidationResult:
"""Holds validation results."""
def __init__(self):
self.errors = []
self.warnings = []
self.info = []
self.is_valid = True
def add_error(self, message: str):
"""Add an error message."""
self.errors.append(message)
self.is_valid = False
def add_warning(self, message: str):
"""Add a warning message."""
self.warnings.append(message)
def add_info(self, message: str):
"""Add an info message."""
self.info.append(message)
def print_results(self):
"""Print validation results."""
if self.info:
print("\n📋 Information:")
for msg in self.info:
print(f" {msg}")
if self.warnings:
print("\n⚠️ Warnings:")
for msg in self.warnings:
print(f" ⚠️ {msg}")
if self.errors:
print("\n❌ Errors:")
for msg in self.errors:
print(f"{msg}")
else:
print("\n✅ No errors found!")
print(f"\nValidation Result: {'✅ PASS' if self.is_valid else '❌ FAIL'}")
def validate_zip_structure(zip_path: Path, config: Dict) -> ValidationResult:
"""Validate the ZIP file structure."""
result = ValidationResult()
addon_id = config['project']['id']
try:
with zipfile.ZipFile(zip_path, 'r') as zip_file:
file_list = zip_file.namelist()
# Check if all files are within the addon directory
addon_root = f"{addon_id}/"
files_in_root = [f for f in file_list if f.startswith(addon_root)]
if len(files_in_root) != len(file_list):
result.add_error(f"Some files are not in the addon root directory '{addon_root}'")
result.add_info(f"ZIP contains {len(file_list)} files")
# Required files
required_files = [
f"{addon_id}/__init__.py",
f"{addon_id}/blender_manifest.toml"
]
for required_file in required_files:
if required_file not in file_list:
result.add_error(f"Missing required file: {required_file}")
else:
result.add_info(f"Found required file: {required_file}")
# Check for excluded files
exclude_patterns = config['build']['exclude_patterns']
for file_path in file_list:
for pattern in exclude_patterns:
if pattern in file_path:
result.add_warning(f"Excluded pattern found in package: {file_path}")
except zipfile.BadZipFile:
result.add_error("Invalid ZIP file")
except Exception as e:
result.add_error(f"Error reading ZIP file: {e}")
return result
def validate_manifest(zip_path: Path, config: Dict, version_type: str) -> ValidationResult:
"""Validate the blender_manifest.toml file."""
result = ValidationResult()
addon_id = config['project']['id']
manifest_path = f"{addon_id}/blender_manifest.toml"
try:
with zipfile.ZipFile(zip_path, 'r') as zip_file:
if manifest_path not in zip_file.namelist():
result.add_error("blender_manifest.toml not found in package")
return result
# Read and parse manifest
manifest_content = zip_file.read(manifest_path).decode('utf-8')
result.add_info("Successfully read blender_manifest.toml")
# Check for required fields
required_fields = ['id', 'version', 'name', 'blender_version_min']
for field in required_fields:
if f'{field} = ' not in manifest_content:
result.add_error(f"Missing required field in manifest: {field}")
# Validate specific values
if f'id = "{addon_id}"' not in manifest_content:
result.add_error(f"Incorrect addon ID in manifest (expected: {addon_id})")
# Check version format
import re
version_pattern = r'version = "(\d+\.\d+\.\d+)"'
version_match = re.search(version_pattern, manifest_content)
if version_match:
version = version_match.group(1)
result.add_info(f"Package version: {version}")
else:
result.add_error("Invalid or missing version format in manifest")
except Exception as e:
result.add_error(f"Error validating manifest: {e}")
return result
def validate_constants(zip_path: Path, config: Dict, version_type: str) -> ValidationResult:
"""Validate the constants.py file for feature flags."""
result = ValidationResult()
addon_id = config['project']['id']
constants_path = f"{addon_id}/utils/constants.py"
try:
with zipfile.ZipFile(zip_path, 'r') as zip_file:
if constants_path not in zip_file.namelist():
result.add_error("utils/constants.py not found in package")
return result
# Read constants file
constants_content = zip_file.read(constants_path).decode('utf-8')
result.add_info("Successfully read utils/constants.py")
# Check for feature flags
if 'ENABLED_FEATURES' not in constants_content:
result.add_error("ENABLED_FEATURES not found in constants.py")
if 'VERSION_TYPE' not in constants_content:
result.add_error("VERSION_TYPE not found in constants.py")
else:
# Check if version type matches expected
if f'VERSION_TYPE = "{version_type}"' not in constants_content:
result.add_warning(f"VERSION_TYPE might not match expected type: {version_type}")
# Check for version limits functions
if 'get_version_limits' not in constants_content:
result.add_error("get_version_limits function not found in constants.py")
except Exception as e:
result.add_error(f"Error validating constants: {e}")
return result
def validate_feature_flags(zip_path: Path, config: Dict) -> ValidationResult:
"""Validate the feature flags system."""
result = ValidationResult()
addon_id = config['project']['id']
feature_flags_path = f"{addon_id}/utils/feature_flags.py"
try:
with zipfile.ZipFile(zip_path, 'r') as zip_file:
if feature_flags_path not in zip_file.namelist():
result.add_warning("utils/feature_flags.py not found in package")
return result
# Read feature flags file
feature_content = zip_file.read(feature_flags_path).decode('utf-8')
result.add_info("Successfully read utils/feature_flags.py")
# Check for essential classes and functions
required_items = [
'FeatureManager',
'is_feature_enabled',
'require_feature',
'feature_required',
'FeatureNotAvailableError'
]
for item in required_items:
if item not in feature_content:
result.add_warning(f"Missing feature flag component: {item}")
else:
result.add_info(f"Found feature flag component: {item}")
except Exception as e:
result.add_error(f"Error validating feature flags: {e}")
return result
def detect_version_type(zip_path: Path) -> Optional[str]:
"""Detect version type from ZIP filename or contents."""
filename = zip_path.name.lower()
if '_free' in filename:
return 'free'
elif 'free' in filename:
return 'free'
else:
return 'full' # Default assumption
def validate_package(zip_path: Path, version_type: Optional[str] = None) -> bool:
"""Validate a complete addon package."""
if not zip_path.exists():
print(f"❌ Package file not found: {zip_path}")
return False
if version_type is None:
version_type = detect_version_type(zip_path)
print(f"🔍 Validating package: {zip_path.name}")
print(f"📋 Detected version type: {version_type}")
print("=" * 60)
config = load_config()
overall_valid = True
# Structure validation
print("Validating ZIP structure...")
structure_result = validate_zip_structure(zip_path, config)
structure_result.print_results()
overall_valid &= structure_result.is_valid
print("\n" + "" * 60)
# Manifest validation
print("Validating blender_manifest.toml...")
manifest_result = validate_manifest(zip_path, config, version_type)
manifest_result.print_results()
overall_valid &= manifest_result.is_valid
print("\n" + "" * 60)
# Constants validation
print("Validating utils/constants.py...")
constants_result = validate_constants(zip_path, config, version_type)
constants_result.print_results()
overall_valid &= constants_result.is_valid
print("\n" + "" * 60)
# Feature flags validation
print("Validating feature flags system...")
features_result = validate_feature_flags(zip_path, config)
features_result.print_results()
overall_valid &= features_result.is_valid
print("\n" + "=" * 60)
print(f"Overall Validation: {'✅ PASS' if overall_valid else '❌ FAIL'}")
return overall_valid
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="Validate Text Texture Generator addon package")
parser.add_argument("package", type=Path, help="Path to ZIP package to validate")
parser.add_argument("--type", choices=["free", "full"], help="Expected version type")
args = parser.parse_args()
try:
is_valid = validate_package(args.package, args.type)
sys.exit(0 if is_valid else 1)
except Exception as e:
print(f"❌ Validation failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()