204 lines
6.2 KiB
Python
204 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GUID Validator for Unity Projects
|
|
|
|
Validates GUIDs in Unity asset files and meta files, identifying common issues:
|
|
- Invalid GUID format (non-hexadecimal characters)
|
|
- Duplicate GUIDs across meta files
|
|
- Missing or orphaned meta files
|
|
- Broken asset references
|
|
|
|
Usage:
|
|
python guid_validator.py <unity_project_path>
|
|
python guid_validator.py <unity_project_path> --fix-orphaned
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
|
|
def is_valid_guid(guid):
|
|
"""Check if GUID is valid 32-character hexadecimal string."""
|
|
return bool(re.match(r"^[0-9a-f]{32}$", guid))
|
|
|
|
|
|
def find_meta_files(project_path):
|
|
"""Find all .meta files in the Assets directory."""
|
|
assets_path = Path(project_path) / "Assets"
|
|
if not assets_path.exists():
|
|
raise ValueError(f"Assets directory not found: {assets_path}")
|
|
return list(assets_path.rglob("*.meta"))
|
|
|
|
|
|
def extract_guid_from_meta(meta_path):
|
|
"""Extract GUID from a .meta file."""
|
|
try:
|
|
with open(meta_path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
if line.startswith("guid:"):
|
|
guid = line.split(":", 1)[1].strip()
|
|
return guid
|
|
except Exception as e:
|
|
print(f"Error reading {meta_path}: {e}")
|
|
return None
|
|
|
|
|
|
def find_asset_references(project_path, guid):
|
|
"""Find all references to a GUID in scene and prefab files."""
|
|
assets_path = Path(project_path) / "Assets"
|
|
references = []
|
|
|
|
for ext in ["*.unity", "*.prefab", "*.asset", "*.mat"]:
|
|
for file_path in assets_path.rglob(ext):
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
if f"guid: {guid}" in content:
|
|
references.append(file_path)
|
|
except Exception:
|
|
pass
|
|
|
|
return references
|
|
|
|
|
|
def check_orphaned_meta_files(meta_files):
|
|
"""Find meta files without corresponding assets."""
|
|
orphaned = []
|
|
for meta_path in meta_files:
|
|
asset_path = Path(str(meta_path)[:-5]) # Remove .meta extension
|
|
if not asset_path.exists():
|
|
orphaned.append(meta_path)
|
|
return orphaned
|
|
|
|
|
|
def check_missing_meta_files(project_path):
|
|
"""Find assets without corresponding meta files."""
|
|
assets_path = Path(project_path) / "Assets"
|
|
missing = []
|
|
|
|
# Skip directories that shouldn't have meta files
|
|
skip_dirs = {".git", ".vs", "Library", "Temp", "obj", "Logs"}
|
|
|
|
for asset_path in assets_path.rglob("*"):
|
|
if asset_path.is_file() and not asset_path.name.endswith(".meta"):
|
|
# Skip files in excluded directories
|
|
if any(skip_dir in asset_path.parts for skip_dir in skip_dirs):
|
|
continue
|
|
|
|
meta_path = Path(str(asset_path) + ".meta")
|
|
if not meta_path.exists():
|
|
missing.append(asset_path)
|
|
|
|
return missing
|
|
|
|
|
|
def validate_guids(project_path, fix_orphaned=False):
|
|
"""Validate all GUIDs in a Unity project."""
|
|
print(f"Validating Unity project: {project_path}\n")
|
|
|
|
meta_files = find_meta_files(project_path)
|
|
print(f"Found {len(meta_files)} meta files\n")
|
|
|
|
# Track GUIDs
|
|
guid_to_files = defaultdict(list)
|
|
invalid_guids = []
|
|
|
|
# Check each meta file
|
|
for meta_path in meta_files:
|
|
guid = extract_guid_from_meta(meta_path)
|
|
if guid:
|
|
if is_valid_guid(guid):
|
|
guid_to_files[guid].append(meta_path)
|
|
else:
|
|
invalid_guids.append((meta_path, guid))
|
|
|
|
# Report invalid GUIDs
|
|
if invalid_guids:
|
|
print("❌ INVALID GUIDs FOUND:")
|
|
for meta_path, guid in invalid_guids:
|
|
print(f" {meta_path}")
|
|
print(f" Invalid GUID: {guid}")
|
|
print(f" Must be 32 hexadecimal characters (0-9, a-f)")
|
|
print()
|
|
else:
|
|
print("✓ All GUIDs have valid format\n")
|
|
|
|
# Report duplicate GUIDs
|
|
duplicates = {
|
|
guid: files for guid, files in guid_to_files.items() if len(files) > 1
|
|
}
|
|
if duplicates:
|
|
print("❌ DUPLICATE GUIDs FOUND:")
|
|
for guid, files in duplicates.items():
|
|
print(f" GUID: {guid}")
|
|
for file_path in files:
|
|
print(f" - {file_path}")
|
|
print()
|
|
else:
|
|
print("✓ No duplicate GUIDs\n")
|
|
|
|
# Check for orphaned meta files
|
|
orphaned = check_orphaned_meta_files(meta_files)
|
|
if orphaned:
|
|
print(f"⚠ ORPHANED META FILES ({len(orphaned)}):")
|
|
for meta_path in orphaned:
|
|
print(f" {meta_path}")
|
|
guid = extract_guid_from_meta(meta_path)
|
|
if guid:
|
|
refs = find_asset_references(project_path, guid)
|
|
if refs:
|
|
print(f" Referenced by: {len(refs)} file(s)")
|
|
print()
|
|
|
|
if fix_orphaned:
|
|
print("Removing orphaned meta files...")
|
|
for meta_path in orphaned:
|
|
meta_path.unlink()
|
|
print(f" Deleted: {meta_path}")
|
|
print()
|
|
else:
|
|
print("✓ No orphaned meta files\n")
|
|
|
|
# Check for missing meta files
|
|
missing = check_missing_meta_files(project_path)
|
|
if missing:
|
|
print(f"⚠ MISSING META FILES ({len(missing)}):")
|
|
for asset_path in missing[:10]: # Show first 10
|
|
print(f" {asset_path}")
|
|
if len(missing) > 10:
|
|
print(f" ... and {len(missing) - 10} more")
|
|
print("\nFix: Open project in Unity Editor to regenerate meta files\n")
|
|
else:
|
|
print("✓ No missing meta files\n")
|
|
|
|
# Summary
|
|
has_errors = bool(invalid_guids or duplicates or orphaned or missing)
|
|
if has_errors:
|
|
print("=" * 60)
|
|
print("VALIDATION FAILED - Issues found")
|
|
print("=" * 60)
|
|
return 1
|
|
else:
|
|
print("=" * 60)
|
|
print("✓ VALIDATION PASSED - No issues found")
|
|
print("=" * 60)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
project_path = sys.argv[1]
|
|
fix_orphaned = "--fix-orphaned" in sys.argv
|
|
|
|
try:
|
|
exit_code = validate_guids(project_path, fix_orphaned=fix_orphaned)
|
|
sys.exit(exit_code)
|
|
except Exception as e:
|
|
print(f"Error: {e}", file=sys.stderr)
|
|
sys.exit(1)
|