diff --git a/build.py b/build.py index ce486531..b77d95a5 100644 --- a/build.py +++ b/build.py @@ -155,7 +155,11 @@ def get_compiler(): compile_python_ver_file(current_platform) # initializing for msvc if wanted as compiler (todo:should be improved sometime) -msvc_init = f"vcvarsall.bat {'x86_amd64'} {command_separator} cl {command_separator} " if "msvc" in args.compiler else "" +if "msvc" in args.compiler: + vcvars_arch = "x64_arm64" if args.target_platform == "windowsarm64" else "x86_amd64" + msvc_init = f"vcvarsall.bat {vcvars_arch} {command_separator} cl {command_separator} " +else: + msvc_init = "" res = None try: @@ -216,6 +220,7 @@ def get_compiler(): shutil.rmtree("build/py4godot") copytree(f"build/final/{args.target_platform}/cpython-3.12.4-{args.target_platform}", f"build/py4godot/cpython-3.12.4-{args.target_platform}") shutil.copy("build_resources/python.gdextension", "build/py4godot/python.gdextension") + shutil.copy("build_resources/plugin.cfg", "build/py4godot/plugin.cfg") shutil.copy("build_resources/dependencies.txt", "build/py4godot/dependencies.txt") shutil.copy("build_resources/install_dependencies.py", "build/py4godot/install_dependencies.py") download_get_pip("build/py4godot") diff --git a/build_resources/plugin.cfg b/build_resources/plugin.cfg new file mode 100644 index 00000000..35cfeca4 --- /dev/null +++ b/build_resources/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Python for Godot (py4godot)" +description="Enables Python scripting in Godot using embedded CPython runtime" +author="py4godot" +version="4.4-alpha7" +script="" \ No newline at end of file diff --git a/build_resources/plugin_tools/README.md b/build_resources/plugin_tools/README.md new file mode 100644 index 00000000..682278dd --- /dev/null +++ b/build_resources/plugin_tools/README.md @@ -0,0 +1,164 @@ +# py4godot Tools Documentation + +This document covers both end-user plugin tools and developer tools for py4godot ARM64 support. + +## Tools Overview + +### End-User Tools (Plugin Distribution) +Located in `addons/py4godot/tools/` when installed: + +``` +addons/py4godot/tools/ +├── diagnostic/ +│ ├── check_dependencies.py # Check for missing dependencies +│ └── test_loading.py # Test DLL loading directly +├── setup/ +│ └── copy_dependencies.py # Copy required runtime libraries +└── README.md # This file +``` + +### Developer Tools (Repository) +Located in `tools/arm64/` for contributors: + +``` +tools/arm64/ +├── check_arm64_dependencies.py # Full dependency analysis +├── test_arm64_loading.py # Advanced DLL loading tests +├── copy_arm64_dependencies_simple.py # Development dependency copier +├── find_ucrt_dlls.py # UCRT DLL locator +└── create_test_project.py # Test project creator +``` + +## Quick Start + +If you're experiencing issues with py4godot on Windows ARM64: + +1. **Check for problems:** + ```bash + cd addons/py4godot/tools/diagnostic + python check_dependencies.py + ``` + +2. **Fix missing dependencies:** + ```bash + cd addons/py4godot/tools/setup + python copy_dependencies.py + ``` + +3. **Advanced troubleshooting:** + ```bash + cd addons/py4godot/tools/diagnostic + python test_loading.py + ``` + +## Tool Descriptions + +### diagnostic/check_dependencies.py +- **Purpose**: Primary diagnostic tool for end users +- **What it does**: + - Checks if all required py4godot files are present + - Verifies DLL architectures (should be ARM64) + - Checks for py4godot_init symbol export + - Validates runtime dependencies +- **When to use**: First step when py4godot isn't working + +### diagnostic/test_loading.py +- **Purpose**: Advanced troubleshooting for persistent issues +- **What it does**: + - Tests direct DLL loading using Python ctypes + - Provides detailed Windows error codes and explanations + - Identifies specific loading failures +- **When to use**: When check_dependencies.py passes but Godot still fails + +### setup/copy_dependencies.py +- **Purpose**: Automatically copy required runtime libraries +- **What it does**: + - Finds Visual C++ Redistributables for ARM64 + - Copies Universal C Runtime (UCRT) DLLs + - Works without requiring Visual Studio tools +- **When to use**: When check_dependencies.py reports missing dependencies + +## Common Issues & Solutions + +### Error 126: Cannot load library +**Cause**: Missing runtime dependencies +**Solution**: Run `copy_dependencies.py` + +### py4godot_init symbol not found +**Cause**: main.dll failed to build properly or wrong architecture +**Solution**: Rebuild py4godot for windowsarm64 + +### Architecture mismatch warnings +**Cause**: x64 DLLs on ARM64 system +**Solution**: Rebuild py4godot with correct target platform + +## System Requirements + +- Windows 11 ARM64 +- Python 3.8+ (any architecture for running tools) +- Visual C++ Redistributables for ARM64 (will be copied by tools if available) +- Windows SDK with ARM64 support (for UCRT DLLs) + +## Getting Help + +If these tools don't resolve your issue: + +1. Run Godot with `--verbose` flag for detailed logging +2. Check Windows Event Viewer for additional error details +3. Report the issue with full diagnostic output + +## Developer Tools (Repository Contributors) + +### Available Tools + +#### `tools/arm64/check_arm64_dependencies.py` +- **Purpose**: Comprehensive dependency analysis with dumpbin integration +- **Usage**: `python tools/arm64/check_arm64_dependencies.py` +- **Features**: Architecture validation, symbol export verification, detailed error reporting +- **Requirements**: Visual Studio tools (dumpbin) + +#### `tools/arm64/test_arm64_loading.py` +- **Purpose**: Advanced DLL loading tests using Python ctypes +- **Usage**: `python tools/arm64/test_arm64_loading.py` +- **Features**: Direct DLL loading simulation, Windows error code analysis + +#### `tools/arm64/copy_arm64_dependencies_simple.py` +- **Purpose**: Development dependency copier +- **Usage**: `python tools/arm64/copy_arm64_dependencies_simple.py` +- **Features**: Interactive mode, works without Visual Studio tools + +#### `tools/arm64/find_ucrt_dlls.py` +- **Purpose**: Specialized UCRT DLL locator and copier +- **Usage**: `python tools/arm64/find_ucrt_dlls.py` +- **Features**: Multi-SDK scanning, batch UCRT copying + +#### `tools/arm64/create_test_project.py` +- **Purpose**: Creates minimal Godot projects for ARM64 testing +- **Usage**: `python tools/arm64/create_test_project.py [project_name]` +- **Features**: Automated project setup, test scripts, debug configuration + +### Development Workflow + +1. **Build for ARM64**: + ```bash + python build.py --target_platform=windowsarm64 --compiler=msvc + ``` + +2. **Verify dependencies**: + ```bash + python tools/arm64/check_arm64_dependencies.py + ``` + +3. **Copy missing dependencies**: + ```bash + python tools/arm64/copy_arm64_dependencies_simple.py + ``` + +4. **Create test project**: + ```bash + python tools/arm64/create_test_project.py my-test + ``` + +### Relationship to End-User Tools + +The developer tools provide the foundation for the simplified end-user tools. Developer tools offer full diagnostic capabilities, while end-user tools are streamlined for plugin distribution. \ No newline at end of file diff --git a/build_resources/plugin_tools/diagnostic/check_dependencies.py b/build_resources/plugin_tools/diagnostic/check_dependencies.py new file mode 100644 index 00000000..3815918c --- /dev/null +++ b/build_resources/plugin_tools/diagnostic/check_dependencies.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +""" +py4godot Dependency Checker +This script helps diagnose missing dependencies for py4godot on Windows ARM64. + +Usage: python check_dependencies.py +""" + +import os +import sys +import subprocess +import platform + +def check_file_exists(filepath): + """Check if a file exists and print its info.""" + if os.path.exists(filepath): + size = os.path.getsize(filepath) + print(f"✓ Found: {os.path.basename(filepath)} ({size:,} bytes)") + return True + else: + print(f"✗ Missing: {os.path.basename(filepath)}") + return False + +def check_dll_architecture(dll_path): + """Check the architecture of a DLL using dumpbin or file command.""" + if not os.path.exists(dll_path): + return "File not found" + + # Try using dumpbin (Visual Studio tool) + try: + result = subprocess.run( + ["dumpbin", "/headers", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + output = result.stdout + if "AA64 machine" in output or "ARM64" in output: + return "ARM64" + elif "8664 machine" in output: + return "x64" + elif "14C machine" in output: + return "x86" + else: + return "Unknown" + except FileNotFoundError: + pass + + return "Cannot determine (dumpbin not available)" + +def check_dll_exports(dll_path): + """Check if py4godot_init is exported from main.dll.""" + if not os.path.exists(dll_path): + return False + + try: + result = subprocess.run( + ["dumpbin", "/exports", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + return "py4godot_init" in result.stdout + except FileNotFoundError: + pass + + return None # Cannot determine + +def find_plugin_directory(): + """Find the py4godot plugin directory.""" + # Look for common locations relative to current directory + possible_paths = [ + ".", # Current directory + "..", # Parent directory + "../..", # Grandparent directory + "addons/py4godot", # Standard Godot addon path + "../addons/py4godot", + "../../addons/py4godot", + ] + + for path in possible_paths: + # Look for python.gdextension file as indicator + gdextension_file = os.path.join(path, "python.gdextension") + if os.path.exists(gdextension_file): + return os.path.abspath(path) + + return None + +def main(): + print("=== py4godot Dependency Checker ===\n") + + # Check system info + print(f"Platform: {platform.platform()}") + print(f"Machine: {platform.machine()}") + print(f"Processor: {platform.processor()}\n") + + # Find plugin directory + plugin_dir = find_plugin_directory() + if not plugin_dir: + print("ERROR: Cannot find py4godot plugin directory!") + print("Please run this script from your Godot project directory or the py4godot plugin directory.") + return 1 + + print(f"Plugin directory: {plugin_dir}\n") + + # Look for ARM64 Python installation + arm64_paths = [ + "cpython-3.12.4-windowsarm64/python", + "arm64/python", + "windowsarm64/python" + ] + + python_dir = None + for path in arm64_paths: + full_path = os.path.join(plugin_dir, path) + if os.path.exists(full_path): + python_dir = full_path + break + + if not python_dir: + print("ERROR: ARM64 Python installation not found!") + print("Expected locations:") + for path in arm64_paths: + print(f" - {os.path.join(plugin_dir, path)}") + print("\nPlease ensure you have the ARM64 build of py4godot installed.") + return 1 + + print(f"Python directory: {python_dir}\n") + + # Key files to check + files_to_check = [ + ("main.dll", "Main py4godot library"), + ("pythonscript.dll", "Python script handler"), + ("python.exe", "Python executable"), + ("python312.dll", "Python runtime library"), + ] + + print("=== Checking Key Files ===") + all_found = True + main_dll_path = None + + for filename, description in files_to_check: + filepath = os.path.join(python_dir, filename) + exists = check_file_exists(filepath) + if not exists: + all_found = False + + if exists and filename.endswith(".dll"): + arch = check_dll_architecture(filepath) + print(f" Architecture: {arch}") + + if arch not in ["ARM64", "Cannot determine (dumpbin not available)"]: + print(f" ⚠️ WARNING: Expected ARM64, found {arch}") + all_found = False + + if filename == "main.dll" and exists: + main_dll_path = filepath + + print() + + # Check main.dll exports + if main_dll_path: + print("=== Checking main.dll Exports ===") + exports_check = check_dll_exports(main_dll_path) + if exports_check is True: + print("✓ py4godot_init symbol found") + elif exports_check is False: + print("✗ py4godot_init symbol NOT FOUND!") + all_found = False + else: + print("? Cannot check exports (dumpbin not available)") + print() + + # Check runtime dependencies + print("=== Checking Runtime Dependencies ===") + runtime_libs = [ + ("vcruntime140.dll", "Visual C++ Runtime"), + ("vcruntime140_1.dll", "Visual C++ Runtime (additional)"), + ("msvcp140.dll", "Visual C++ Standard Library"), + ("ucrtbase.dll", "Universal C Runtime"), + ] + + missing_deps = [] + for lib, desc in runtime_libs: + lib_path = os.path.join(python_dir, lib) + exists = check_file_exists(lib_path) + if exists: + arch = check_dll_architecture(lib_path) + print(f" Architecture: {arch}") + if arch not in ["ARM64", "Cannot determine (dumpbin not available)"]: + print(f" ⚠️ WARNING: Expected ARM64, found {arch}") + missing_deps.append(lib) + else: + missing_deps.append(lib) + print() + + # Provide recommendations + print("=== Diagnosis & Recommendations ===") + + if all_found and not missing_deps: + print("✅ All dependencies appear to be in place!") + print("If you're still experiencing issues:") + print("1. Make sure Godot is running from the project directory") + print("2. Use --verbose flag when running Godot") + print("3. Check Godot console for detailed error messages") + else: + print("❌ Issues detected:") + + if not all_found: + print("• Core py4godot files are missing or have wrong architecture") + print(" → Solution: Rebuild py4godot for windowsarm64 platform") + + if missing_deps: + print("• Missing runtime dependencies:") + for dep in missing_deps: + print(f" - {dep}") + print(" → Solution: Run the dependency copier script") + print(" python ../setup/copy_dependencies.py") + + print("\n=== Next Steps ===") + if missing_deps: + print("1. Run: python ../setup/copy_dependencies.py") + print("2. Run this checker again to verify") + print("3. Test in Godot with --verbose logging") + else: + print("1. Test in Godot with --verbose logging") + print("2. If issues persist, run: python test_loading.py") + + return 0 if all_found and not missing_deps else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/build_resources/plugin_tools/diagnostic/test_loading.py b/build_resources/plugin_tools/diagnostic/test_loading.py new file mode 100644 index 00000000..521d570f --- /dev/null +++ b/build_resources/plugin_tools/diagnostic/test_loading.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +py4godot DLL Loading Test +This script tests if py4godot DLLs can be loaded directly for advanced troubleshooting. + +Usage: python test_loading.py +""" + +import os +import sys +import ctypes +from ctypes import wintypes + +def test_load_library(dll_path, dll_name): + """Test loading a DLL and report detailed errors.""" + print(f"\nTesting: {dll_name}") + print(f"Path: {dll_path}") + + if not os.path.exists(dll_path): + print(f"✗ File does not exist!") + return False + + # Get absolute path + abs_path = os.path.abspath(dll_path) + + # Try to load with ctypes + try: + # First, try setting the DLL directory + kernel32 = ctypes.windll.kernel32 + kernel32.SetDllDirectoryW.argtypes = [wintypes.LPCWSTR] + kernel32.SetDllDirectoryW.restype = wintypes.BOOL + + dll_dir = os.path.dirname(abs_path) + if kernel32.SetDllDirectoryW(dll_dir): + print(f"✓ Set DLL directory to: {dll_dir}") + + # Try loading the library + print("Attempting to load library...") + lib = ctypes.CDLL(abs_path) + print("✓ Library loaded successfully!") + + # For main.dll, try to find py4godot_init + if dll_name == "main.dll": + try: + py4godot_init = lib.py4godot_init + print("✓ Found py4godot_init symbol!") + return True + except AttributeError: + print("✗ py4godot_init symbol not found!") + return False + + return True + + except OSError as e: + print(f"✗ Failed to load library: {e}") + + # Get more detailed error info + error_code = ctypes.get_last_error() + if error_code: + print(f"Windows error code: {error_code}") + + # Provide user-friendly error explanations + error_messages = { + 126: "Module or one of its dependencies could not be found", + 127: "The specified procedure could not be found", + 193: "Not a valid Win32 application (architecture mismatch)", + 1114: "A dynamic link library (DLL) initialization routine failed" + } + + if error_code in error_messages: + print(f"Error meaning: {error_messages[error_code]}") + + if error_code == 126: + print("\n💡 Error 126 Solutions:") + print("1. Run: python ../setup/copy_dependencies.py") + print("2. Install Visual C++ Redistributables for ARM64") + print("3. Check that all DLLs are ARM64 architecture") + elif error_code == 193: + print("\n💡 Error 193 Solutions:") + print("1. Verify all DLLs are ARM64 (not x64 or x86)") + print("2. Rebuild py4godot for windowsarm64 platform") + + return False + +def find_plugin_directory(): + """Find the py4godot plugin directory.""" + # Look for common locations relative to current directory + possible_paths = [ + ".", # Current directory + "..", # Parent directory + "../..", # Grandparent directory + "addons/py4godot", # Standard Godot addon path + "../addons/py4godot", + "../../addons/py4godot", + ] + + for path in possible_paths: + # Look for python.gdextension file as indicator + gdextension_file = os.path.join(path, "python.gdextension") + if os.path.exists(gdextension_file): + return os.path.abspath(path) + + return None + +def main(): + print("=== py4godot DLL Loading Test ===\n") + + # Check Python environment + print(f"Python version: {sys.version}") + print(f"Python executable: {sys.executable}") + + # Find plugin directory + plugin_dir = find_plugin_directory() + if not plugin_dir: + print("\nERROR: Cannot find py4godot plugin directory!") + print("Please run this script from your Godot project directory or the py4godot plugin directory.") + return 1 + + print(f"Plugin directory: {plugin_dir}") + + # Look for ARM64 Python installation + arm64_paths = [ + "cpython-3.12.4-windowsarm64/python", + "arm64/python", + "windowsarm64/python" + ] + + python_dir = None + for path in arm64_paths: + full_path = os.path.join(plugin_dir, path) + if os.path.exists(full_path): + python_dir = full_path + break + + if not python_dir: + print("\nERROR: ARM64 Python installation not found!") + return 1 + + print(f"Python directory: {python_dir}") + + # Test loading libraries in order + libraries_to_test = [ + ("python312.dll", "Python Runtime"), + ("pythonscript.dll", "Python Script Handler"), + ("main.dll", "Main py4godot Library"), + ] + + success_count = 0 + total_count = len(libraries_to_test) + + for dll_name, description in libraries_to_test: + dll_path = os.path.join(python_dir, dll_name) + if os.path.exists(dll_path): + success = test_load_library(dll_path, dll_name) + if success: + success_count += 1 + else: + print(f"\n✗ {dll_name} not found at: {dll_path}") + + # Summary + print(f"\n=== Results ===") + print(f"Successfully loaded: {success_count}/{total_count} libraries") + + if success_count == total_count: + print("✅ All libraries loaded successfully!") + print("The DLLs appear to be working correctly.") + print("If Godot still has issues, the problem may be elsewhere.") + else: + print("❌ Some libraries failed to load.") + print("\n💡 Recommended actions:") + print("1. Run: python ../setup/copy_dependencies.py") + print("2. Run: python check_dependencies.py") + print("3. Install Visual C++ Redistributables for ARM64") + print("4. Verify all files are ARM64 architecture") + + print("\n=== Additional Help ===") + print("• For dependency issues: Use copy_dependencies.py") + print("• For architecture issues: Rebuild py4godot for windowsarm64") + print("• For Godot issues: Run Godot with --verbose flag") + + return 0 if success_count == total_count else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/build_resources/plugin_tools/setup/copy_dependencies.py b/build_resources/plugin_tools/setup/copy_dependencies.py new file mode 100644 index 00000000..bed344f6 --- /dev/null +++ b/build_resources/plugin_tools/setup/copy_dependencies.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +""" +py4godot Dependencies Copier +This script copies required Visual C++ runtime dependencies for py4godot on Windows ARM64. + +Usage: python copy_dependencies.py +""" + +import os +import shutil +import sys +import glob +from pathlib import Path + +def copy_file_if_exists(source, dest, name): + """Copy a file if it exists, return True if successful.""" + if not os.path.exists(source): + print(f"✗ NOT FOUND: {name}") + return False + + if os.path.exists(dest): + print(f"✓ Already exists: {name}") + return True + + try: + shutil.copy2(source, dest) + size = os.path.getsize(dest) + print(f"✓ Copied: {name} ({size:,} bytes)") + return True + except Exception as e: + print(f"✗ Failed to copy {name}: {e}") + return False + +def find_visual_cpp_redist(): + """Find Visual C++ Redistributable DLLs for ARM64.""" + search_paths = [ + # System directory (if ARM64 redistributables are installed) + r"C:\Windows\System32", + + # Visual Studio 2022 locations + r"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + + # x86 Program Files locations + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\*\arm64\Microsoft.VC143.CRT", + ] + + found_paths = {} + + for pattern in search_paths: + for path in glob.glob(pattern): + if os.path.exists(path): + # Check what DLLs are available in this path + available_dlls = [] + for dll in ["vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll"]: + dll_path = os.path.join(path, dll) + if os.path.exists(dll_path): + available_dlls.append(dll) + + if available_dlls: + found_paths[path] = available_dlls + + return found_paths + +def find_ucrt_dlls(): + """Find Universal C Runtime DLLs for ARM64.""" + search_paths = [ + # System directory + r"C:\Windows\System32", + + # Windows SDK locations + r"C:\Program Files (x86)\Windows Kits\10\Redist\*\ucrt\DLLs\arm64", + r"C:\Program Files\Windows Kits\10\Redist\*\ucrt\DLLs\arm64", + r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\arm64", + r"C:\Program Files\Windows Kits\10\Redist\ucrt\DLLs\arm64", + ] + + for pattern in search_paths: + for path in glob.glob(pattern): + if os.path.exists(path): + # Check for ucrtbase.dll and api-ms-win-crt-*.dll + ucrtbase = os.path.join(path, "ucrtbase.dll") + api_dlls = glob.glob(os.path.join(path, "api-ms-win-crt-*.dll")) + + if os.path.exists(ucrtbase) or api_dlls: + return path + + return None + +def find_plugin_directory(): + """Find the py4godot plugin directory.""" + # Look for common locations relative to current directory + possible_paths = [ + ".", # Current directory + "..", # Parent directory + "../..", # Grandparent directory + "addons/py4godot", # Standard Godot addon path + "../addons/py4godot", + "../../addons/py4godot", + ] + + for path in possible_paths: + # Look for python.gdextension file as indicator + gdextension_file = os.path.join(path, "python.gdextension") + if os.path.exists(gdextension_file): + return os.path.abspath(path) + + return None + +def main(): + print("=== py4godot Dependencies Copier ===\n") + + # Find plugin directory + plugin_dir = find_plugin_directory() + if not plugin_dir: + print("ERROR: Cannot find py4godot plugin directory!") + print("Please run this script from your Godot project directory or the py4godot plugin directory.") + return 1 + + print(f"Plugin directory: {plugin_dir}") + + # Look for ARM64 Python installation + arm64_paths = [ + "cpython-3.12.4-windowsarm64/python", + "arm64/python", + "windowsarm64/python" + ] + + target_dir = None + for path in arm64_paths: + full_path = os.path.join(plugin_dir, path) + if os.path.exists(full_path): + target_dir = full_path + break + + if not target_dir: + print("ERROR: ARM64 Python installation not found!") + print("Expected locations:") + for path in arm64_paths: + print(f" - {os.path.join(plugin_dir, path)}") + return 1 + + print(f"Target directory: {target_dir}\n") + + copied_count = 0 + + # Copy Visual C++ Runtime DLLs + print("=== Copying Visual C++ Runtime DLLs ===") + vc_paths = find_visual_cpp_redist() + + if not vc_paths: + print("⚠️ No Visual C++ ARM64 redistributables found!") + print("Please install Visual C++ Redistributables for ARM64:") + print("https://aka.ms/vs/17/release/vc_redist.arm64.exe") + else: + print(f"Found {len(vc_paths)} potential source(s):") + for path, dlls in vc_paths.items(): + print(f" {path}: {', '.join(dlls)}") + + # Use the first available path with the most DLLs + best_path = max(vc_paths.items(), key=lambda x: len(x[1]))[0] + print(f"\nUsing: {best_path}") + + required_dlls = ["vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll"] + for dll in required_dlls: + source = os.path.join(best_path, dll) + dest = os.path.join(target_dir, dll) + if copy_file_if_exists(source, dest, dll): + copied_count += 1 + + print() + + # Copy Universal C Runtime DLLs + print("=== Copying Universal C Runtime DLLs ===") + ucrt_path = find_ucrt_dlls() + + if not ucrt_path: + print("⚠️ No Universal C Runtime ARM64 DLLs found!") + print("Please install Windows SDK with ARM64 support:") + print("https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/") + else: + print(f"Found UCRT path: {ucrt_path}") + + # Copy ucrtbase.dll + ucrtbase_source = os.path.join(ucrt_path, "ucrtbase.dll") + ucrtbase_dest = os.path.join(target_dir, "ucrtbase.dll") + if copy_file_if_exists(ucrtbase_source, ucrtbase_dest, "ucrtbase.dll"): + copied_count += 1 + + # Copy api-ms-win-crt-*.dll files + api_dlls = glob.glob(os.path.join(ucrt_path, "api-ms-win-crt-*.dll")) + print(f"\nCopying {len(api_dlls)} API DLLs...") + for api_dll in api_dlls: + dll_name = os.path.basename(api_dll) + dest = os.path.join(target_dir, dll_name) + if copy_file_if_exists(api_dll, dest, dll_name): + copied_count += 1 + + # Summary + print(f"\n=== Summary ===") + if copied_count > 0: + print(f"✅ Successfully copied {copied_count} file(s)!") + print("\nNext steps:") + print("1. Run: python ../diagnostic/check_dependencies.py") + print("2. Test py4godot in Godot with --verbose logging") + else: + print("⚠️ No new files were copied.") + print("This could mean:") + print("• Dependencies are already present") + print("• Required redistributables are not installed") + print("• ARM64 versions are not available on this system") + + print("\nManual installation options:") + print("1. Install Visual C++ Redistributables for ARM64") + print("2. Install Windows SDK with ARM64 support") + print("3. Copy DLLs manually from another ARM64 system") + + return 0 if copied_count > 0 else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/build_resources/python.gdextension b/build_resources/python.gdextension index 126b784b..e726137b 100644 --- a/build_resources/python.gdextension +++ b/build_resources/python.gdextension @@ -9,6 +9,8 @@ windows.debug.x86_64 = "cpython-3.12.4-windows64/python/pythonscript.dll" windows.release.x86_64 = "cpython-3.12.4-windows64/python/pythonscript.dll" windows.debug.x86 = "cpython-3.12.4-windows32/python/pythonscript.dll" windows.release.x86 = "cpython-3.12.4-windows32/python/pythonscript.dll" +windows.debug.arm64 = "cpython-3.12.4-windowsarm64/python/pythonscript.dll" +windows.release.arm64 = "cpython-3.12.4-windowsarm64/python/pythonscript.dll" linux.debug.x86_64 = "cpython-3.12.4-linux64/python/bin/pythonscript.so" diff --git a/meson.build b/meson.build index eddc4cb4..a9ad2d07 100644 --- a/meson.build +++ b/meson.build @@ -115,9 +115,21 @@ godot_program = find_program('godot', required:false) sources = glob.stdout().strip().split('\n') cppsources = glob_cpp.stdout().strip().split('\n') + +# Add .def file for Windows to ensure symbol exports +main_link_args = link_args +if host_machine.system() == 'windows' + main_def_file = meson.current_source_dir() / 'py4godot' / 'godot_bindings' / 'main.def' + if meson.get_compiler('cpp').get_id() == 'msvc' + main_link_args += ['/DEF:' + main_def_file] + else + main_link_args += ['-Wl,--output-def,' + main_def_file] + endif +endif + main_lib = shared_library('main',cppsources, dependencies:[dep_py], include_directories:internal_inc,name_prefix:'',c_args:extra_args, - link_args : link_args, + link_args : main_link_args, cpp_args:cpp_args, install_rpath: '$ORIGIN/../lib', build_rpath : '$ORIGIN/../lib') diff --git a/meson_scripts/copy_tools.py b/meson_scripts/copy_tools.py index 079e5443..c4edf9e4 100644 --- a/meson_scripts/copy_tools.py +++ b/meson_scripts/copy_tools.py @@ -13,8 +13,7 @@ def strip_platform(text): text = text[1:] - return text.lstrip("linuxarm64").lstrip("linux64").lstrip("windows64").lstrip("windows32").lstrip("linux32").lstrip("darwin64") - + return text.lstrip("linuxarm64").lstrip("linux64").lstrip("windows64").lstrip("windows32").lstrip("linux32").lstrip("darwin64").lstrip("linuxarm64").lstrip("windowsarm64") def run(platform): # copying all the files from build to the folder of the addon diff --git a/meson_scripts/download_python.py b/meson_scripts/download_python.py index 52695526..3f270460 100644 --- a/meson_scripts/download_python.py +++ b/meson_scripts/download_python.py @@ -11,7 +11,7 @@ platform_dict = {"windows64": "x86_64-pc-windows-msvc-install_only_stripped", "windows32": "i686-pc-windows-msvc-install_only_stripped", "linux64": "x86_64-unknown-linux-gnu-install_only_stripped", "darwin64":"aarch64-apple-darwin-install_only_stripped", - "linuxarm64":"aarch64-unknown-linux-gnu-install_only_stripped"} + "linuxarm64":"aarch64-unknown-linux-gnu-install_only_stripped", "windowsarm64":"aarch64-pc-windows-msvc-install_only_stripped"} python_files_dir = "python_files" copy_dir = "build/final" python_ver = "cpython-3.12.4" @@ -31,12 +31,15 @@ def download_file(platform, allow_copy=False): print("download:" + platform) - if platform != "linux32" and platform != "linux64": + if platform != "linux32" and platform != "linux64" and platform != "windowsarm64": url = f'https://github.com/indygreg/python-build-standalone/releases/download/20240726/{python_ver}+20240726-{platform_dict[platform]}.tar.gz' python_file = f'{python_files_dir}/{python_ver}-{platform_dict[platform]}.tar.gz' elif platform == "linux64": url = f'https://github.com/niklas2902/prebuild-python-linux64/releases/download/release-0.1/{python_ver}-linux64.zip' python_file = f'{python_files_dir}/{python_ver}-linux64.zip' + elif platform == "windowsarm64": + url = f'https://github.com/niklas2902/prebuilt-windowsarm64/releases/download/release-0.1/{python_ver}-windowsarm64.zip' + python_file = f'{python_files_dir}/{python_ver}-windowsarm64.zip' else : url = f'https://github.com/niklas2902/prebuild-python-linux32/releases/download/release-0.1/{python_ver}-linux32.zip' python_file = f'{python_files_dir}/{python_ver}-linux32.zip' diff --git a/meson_scripts/platform_check.py b/meson_scripts/platform_check.py index be467a5b..6d86e54d 100644 --- a/meson_scripts/platform_check.py +++ b/meson_scripts/platform_check.py @@ -11,7 +11,12 @@ def get_platform(): return "linuxarm64" elif struct.calcsize("P") * 8 == 64: return "linux64" - + + if system == "windows": + if arch in ("aarch64", "arm64"): + return "windowsarm64" + elif struct.calcsize("P") * 8 == 64: + return "windows64" + # Fallback for other systems if needed return f"{system}{struct.calcsize('P') * 8}" - diff --git a/platforms/windowsarm64.cross b/platforms/windowsarm64.cross new file mode 100644 index 00000000..8e5e4160 --- /dev/null +++ b/platforms/windowsarm64.cross @@ -0,0 +1,13 @@ +[binaries] + ar = 'lib' +strip = 'strip' +exe_wrapper = '' # Empty unless you need to run ARM64 binaries on a different host. + +[host_machine] +system = 'windows' +cpu_family = 'aarch64' +cpu = 'aarch64' +endian = 'little' + +[properties] +current_platform = 'Windows' \ No newline at end of file diff --git a/py4godot/godot_bindings/main.def b/py4godot/godot_bindings/main.def new file mode 100644 index 00000000..c0b703b7 --- /dev/null +++ b/py4godot/godot_bindings/main.def @@ -0,0 +1,3 @@ +LIBRARY main +EXPORTS + py4godot_init \ No newline at end of file diff --git a/py4godot/godot_bindings/main.h b/py4godot/godot_bindings/main.h index 3a83000c..22472dcf 100644 --- a/py4godot/godot_bindings/main.h +++ b/py4godot/godot_bindings/main.h @@ -5,7 +5,11 @@ #include "Python.h" -#ifdef _WIN64 +#if defined(_WIN64) && defined(_M_ARM64) +#define PYTHONHOME L"addons/py4godot/cpython-3.12.4-windowsarm64/python" +#define PYTHONPATH "addons/py4godot/cpython-3.12.4-windowsarm64/python/Lib/site-packages" + +#elif defined(_WIN64) #define PYTHONHOME L"addons/py4godot/cpython-3.12.4-windows64/python" #define PYTHONPATH "addons/py4godot/cpython-3.12.4-windows64/python/Lib/site-packages" diff --git a/py4godot/godot_bindings/pythonscript.cpp b/py4godot/godot_bindings/pythonscript.cpp index 10657669..e344bfa8 100644 --- a/py4godot/godot_bindings/pythonscript.cpp +++ b/py4godot/godot_bindings/pythonscript.cpp @@ -4,17 +4,41 @@ #if defined(__linux__) || defined(__APPLE__) #include #include // For dlopen, dlsym, dlclose on Linux/macOS +#include // For dlopen, dlsym, dlclose on Linux/macOS +#elif defined(_WIN32) || defined(_WIN64) +#include +#include // For LoadLibrary, GetProcAddress, FreeLibrary on Windows #endif typedef GDExtensionBool (*Py4GodotInitFunc)(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); +std::string get_library_path() { + #if defined(__linux__) + return "addons/py4godot/cpython-3.12.4-linux64/python/bin/main.so"; + #elif defined(__APPLE__) + return "addons/py4godot/cpython-3.12.4-darwin64/python/bin/main.dylib"; + #elif defined(_WIN32) || defined(_WIN64) + #if defined(_M_ARM64) + return "addons/py4godot/cpython-3.12.4-windowsarm64/python/main.dll"; + #else + return "addons/py4godot/cpython-3.12.4-windows64/python/main.dll"; + #endif + #else + return ""; // Unsupported platform + #endif +} + extern "C" { - // Entry point for initializing Python script extension GDExtensionBool GDN_EXPORT initialize_pythonscript(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + std::string library_path = get_library_path(); + if (library_path.empty()) { + std::cerr << "Unsupported platform or architecture." << std::endl; + return 1; + } #if defined(__linux__) void* handle = nullptr; @@ -33,40 +57,118 @@ extern "C" { #endif #if defined(__linux__) || defined(__APPLE__) + void* handle = dlopen(library_path.c_str(), RTLD_NOW | RTLD_GLOBAL); if (!handle) { std::cerr << "Cannot load library: " << dlerror() << std::endl; return 1; } - // Clear any existing errors - dlerror(); - - // Load the py4godot_init symbol - Py4GodotInitFunc load_function_handle = (Py4GodotInitFunc) dlsym(handle, "py4godot_init"); - - // Check for dlsym errors + dlerror(); // Clear any existing errors + Py4GodotInitFunc load_function_handle = (Py4GodotInitFunc)dlsym(handle, "py4godot_init"); const char* dlsym_error = dlerror(); if (dlsym_error) { std::cerr << "Cannot load symbol 'py4godot_init': " << dlsym_error << std::endl; - dlclose(handle); // Close the library before exiting + dlclose(handle); return 1; } - // Call the loaded function int result = load_function_handle(p_get_proc_address, p_library, r_initialization); - - // Close the library handle only on linux (crash on macos) #if defined(__linux__) -// dlclose(handle); + // Uncomment if safe to close the library on Linux + // dlclose(handle); #endif return result; - #endif - #ifdef _WIN64 - // Direct call on Windows, where dynamic linking is handled differently - py4godot_init(p_get_proc_address, p_library, r_initialization); - return 1; - #endif + #elif defined(_WIN32) || defined(_WIN64) + // Log the full path being loaded + std::cerr << "[py4godot] Attempting to load library: " << library_path << std::endl; + + // Get absolute path for debugging + char fullPath[MAX_PATH]; + if (GetFullPathNameA(library_path.c_str(), MAX_PATH, fullPath, nullptr)) { + std::cerr << "[py4godot] Full path: " << fullPath << std::endl; + + // Extract directory from full path + std::string fullPathStr(fullPath); + size_t lastSlash = fullPathStr.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + std::string dirPath = fullPathStr.substr(0, lastSlash); + std::cerr << "[py4godot] Setting DLL directory to: " << dirPath << std::endl; + + // Convert to wide string for SetDllDirectoryW + int size_needed = MultiByteToWideChar(CP_UTF8, 0, dirPath.c_str(), -1, NULL, 0); + std::wstring wideDirPath(size_needed - 1, 0); + MultiByteToWideChar(CP_UTF8, 0, dirPath.c_str(), -1, &wideDirPath[0], size_needed); + + // Set the DLL search directory + if (SetDllDirectoryW(wideDirPath.c_str())) { + std::cerr << "[py4godot] Successfully set DLL directory" << std::endl; + } else { + std::cerr << "[py4godot] Failed to set DLL directory, error: " << GetLastError() << std::endl; + } + } + } + + HMODULE handle = LoadLibraryA(library_path.c_str()); + if (!handle) { + DWORD error = GetLastError(); + std::cerr << "[py4godot] ERROR: Cannot load library. Error code: " << error << std::endl; + + // Get detailed error message + LPVOID lpMsgBuf; + DWORD bufLen = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL); + + if (bufLen) { + std::cerr << "[py4godot] Error details: " << (char*)lpMsgBuf << std::endl; + LocalFree(lpMsgBuf); + } + + // Check if file exists + DWORD fileAttr = GetFileAttributesA(library_path.c_str()); + if (fileAttr == INVALID_FILE_ATTRIBUTES) { + std::cerr << "[py4godot] File does not exist or cannot be accessed at: " << library_path << std::endl; + } else { + std::cerr << "[py4godot] File exists but cannot be loaded. This may indicate missing dependencies." << std::endl; + } + + return 1; + } + + std::cerr << "[py4godot] Library loaded successfully. Looking for 'py4godot_init' symbol..." << std::endl; + + Py4GodotInitFunc load_function_handle = (Py4GodotInitFunc)GetProcAddress(handle, "py4godot_init"); + if (!load_function_handle) { + DWORD error = GetLastError(); + std::cerr << "[py4godot] ERROR: Cannot load symbol 'py4godot_init'. Error code: " << error << std::endl; + + // Get detailed error message + LPVOID lpMsgBuf; + DWORD bufLen = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL); + + if (bufLen) { + std::cerr << "[py4godot] Error details: " << (char*)lpMsgBuf << std::endl; + LocalFree(lpMsgBuf); + } + + FreeLibrary(handle); + return 1; + } + int result = load_function_handle(p_get_proc_address, p_library, r_initialization); + // Keep the library loaded for the application's lifetime + return result; + #endif } } \ No newline at end of file diff --git a/tools/arm64/check_arm64_dependencies.py b/tools/arm64/check_arm64_dependencies.py new file mode 100644 index 00000000..422f6917 --- /dev/null +++ b/tools/arm64/check_arm64_dependencies.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +ARM64 Dependency Checker for py4godot +This script helps diagnose missing dependencies for Windows ARM64 builds. +""" + +import os +import sys +import subprocess +import platform + +def check_file_exists(filepath): + """Check if a file exists and print its info.""" + if os.path.exists(filepath): + size = os.path.getsize(filepath) + print(f"✓ Found: {filepath} ({size:,} bytes)") + return True + else: + print(f"✗ Missing: {filepath}") + return False + +def check_dll_architecture(dll_path): + """Check the architecture of a DLL using dumpbin or file command.""" + if not os.path.exists(dll_path): + return "File not found" + + # Try using dumpbin (Visual Studio tool) + try: + result = subprocess.run( + ["dumpbin", "/headers", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + output = result.stdout + if "AA64 machine" in output or "ARM64" in output: + return "ARM64" + elif "8664 machine" in output: + return "x64" + elif "14C machine" in output: + return "x86" + else: + return "Unknown" + except FileNotFoundError: + pass + + # Try using file command (WSL/Linux) + try: + result = subprocess.run( + ["file", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + output = result.stdout + if "ARM64" in output or "Aarch64" in output: + return "ARM64" + elif "x86-64" in output: + return "x64" + elif "80386" in output: + return "x86" + else: + return "Unknown" + except FileNotFoundError: + pass + + return "Cannot determine (tools not available)" + +def check_dll_exports(dll_path): + """Check exported symbols from a DLL.""" + if not os.path.exists(dll_path): + return [] + + try: + result = subprocess.run( + ["dumpbin", "/exports", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + exports = [] + lines = result.stdout.split('\n') + in_exports = False + for line in lines: + if "ordinal hint" in line.lower(): + in_exports = True + continue + if in_exports and line.strip(): + parts = line.split() + if len(parts) >= 4: + exports.append(parts[-1]) + return exports + except FileNotFoundError: + pass + + return [] + +def check_dll_dependencies(dll_path): + """Check dependencies of a DLL.""" + if not os.path.exists(dll_path): + return [] + + try: + result = subprocess.run( + ["dumpbin", "/dependents", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + deps = [] + lines = result.stdout.split('\n') + for line in lines: + line = line.strip() + if line.endswith('.dll') or line.endswith('.DLL'): + deps.append(line) + return deps + except FileNotFoundError: + pass + + return [] + +def main(): + print("=== py4godot Windows ARM64 Dependency Checker ===\n") + + # Check system info + print(f"Platform: {platform.platform()}") + print(f"Machine: {platform.machine()}") + print(f"Processor: {platform.processor()}\n") + + # Define paths to check (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + base_path = os.path.join(repo_root, "build/final/windowsarm64/cpython-3.12.4-windowsarm64/python") + + if not os.path.exists(base_path): + print(f"ERROR: Build directory not found: {base_path}") + print("Please run the build first from repository root:") + print("python build.py --target_platform=windowsarm64 --compiler=msvc") + return 1 + + # Key files to check + files_to_check = [ + os.path.join(base_path, "main.dll"), + os.path.join(base_path, "pythonscript.dll"), + os.path.join(base_path, "python.exe"), + os.path.join(base_path, "python312.dll"), + ] + + print("=== Checking Key Files ===") + for filepath in files_to_check: + if check_file_exists(filepath): + arch = check_dll_architecture(filepath) + print(f" Architecture: {arch}") + + # Check exports for main.dll + if filepath.endswith("main.dll"): + print("\n Checking exports from main.dll:") + exports = check_dll_exports(filepath) + if exports: + for exp in exports: + if "py4godot" in exp.lower(): + print(f" ✓ {exp}") + if not any("py4godot_init" in exp for exp in exports): + print(" ✗ py4godot_init NOT FOUND in exports!") + else: + print(" Could not check exports (dumpbin not available)") + + print("\n Checking dependencies of main.dll:") + deps = check_dll_dependencies(filepath) + if deps: + for dep in deps: + print(f" - {dep}") + else: + print(" Could not check dependencies (dumpbin not available)") + print() + + # Check Python libraries + print("\n=== Checking Python Runtime Libraries ===") + python_libs = [ + "python312.dll", + "vcruntime140.dll", + "vcruntime140_1.dll", + "msvcp140.dll", + "ucrtbase.dll", + "api-ms-win-crt-runtime-l1-1-0.dll", + ] + + for lib in python_libs: + lib_path = os.path.join(base_path, lib) + if check_file_exists(lib_path): + arch = check_dll_architecture(lib_path) + print(f" Architecture: {arch}") + + # Check for ARM64 Visual C++ Redistributables + print("\n=== Checking Visual C++ Redistributables ===") + vc_redist_paths = [ + r"C:\Windows\System32\vcruntime140.dll", + r"C:\Windows\System32\msvcp140.dll", + ] + + for path in vc_redist_paths: + if check_file_exists(path): + arch = check_dll_architecture(path) + print(f" Architecture: {arch}") + if arch != "ARM64": + print(f" ⚠️ WARNING: System redistributable is {arch}, but we need ARM64!") + + print("\n=== Recommendations ===") + print("1. If main.dll exists but doesn't export py4godot_init, the build may have failed to link properly.") + print("2. If dependencies show x64 DLLs instead of ARM64, there's an architecture mismatch.") + print("3. Install Visual C++ Redistributables for ARM64 if missing.") + print("4. Use Dependency Walker or Dependencies.exe for more detailed analysis.") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/arm64/copy_arm64_dependencies.py b/tools/arm64/copy_arm64_dependencies.py new file mode 100644 index 00000000..d2be8208 --- /dev/null +++ b/tools/arm64/copy_arm64_dependencies.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +Copy ARM64 Dependencies for py4godot +This script copies missing runtime dependencies to the build directory. +""" + +import os +import shutil +import subprocess +import sys +from pathlib import Path + +def get_dll_dependencies(dll_path): + """Get list of dependencies for a DLL using dumpbin.""" + dependencies = [] + try: + result = subprocess.run( + ["dumpbin", "/dependents", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + lines = result.stdout.split('\n') + in_deps = False + for line in lines: + line = line.strip() + if "Image has the following dependencies:" in line: + in_deps = True + continue + if in_deps and line and (line.endswith('.dll') or line.endswith('.DLL')): + dependencies.append(line) + if "Summary" in line: + break + return dependencies + except Exception as e: + print(f"Error getting dependencies: {e}") + return dependencies + +def find_dll_in_system(dll_name): + """Find a DLL in common system locations.""" + search_paths = [ + r"C:\Windows\System32", + r"C:\Windows\SysWOW64", + r"C:\Windows\System", + r"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22621.0\ucrt\DLLs\arm64", + r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\arm64", + ] + + for path in search_paths: + if os.path.exists(path): + dll_path = os.path.join(path, dll_name) + if os.path.exists(dll_path): + return dll_path + + # Try to find using where command + try: + result = subprocess.run( + ["where", dll_name], + capture_output=True, + text=True + ) + if result.returncode == 0: + paths = result.stdout.strip().split('\n') + if paths and paths[0]: + return paths[0] + except: + pass + + return None + +def check_dll_architecture(dll_path): + """Check if DLL is ARM64.""" + try: + result = subprocess.run( + ["dumpbin", "/headers", dll_path], + capture_output=True, + text=True + ) + if result.returncode == 0: + if "AA64 machine" in result.stdout or "ARM64" in result.stdout: + return "ARM64" + elif "8664 machine" in result.stdout: + return "x64" + elif "14C machine" in result.stdout: + return "x86" + except: + pass + return "Unknown" + +def copy_dependencies(target_dir): + """Copy all missing dependencies to target directory.""" + print(f"=== Copying ARM64 Dependencies to {target_dir} ===\n") + + if not os.path.exists(target_dir): + print(f"ERROR: Target directory does not exist: {target_dir}") + return False + + # Get main.dll path + main_dll = os.path.join(target_dir, "main.dll") + if not os.path.exists(main_dll): + print(f"ERROR: main.dll not found at: {main_dll}") + return False + + # Get dependencies + print("Getting dependencies of main.dll...") + dependencies = get_dll_dependencies(main_dll) + print(f"Found {len(dependencies)} dependencies\n") + + # Core runtime DLLs that should be copied + essential_dlls = [ + "msvcp140.dll", + "vcruntime140.dll", + "vcruntime140_1.dll", + "ucrtbase.dll", + ] + + # Add all api-ms-win-crt DLLs + for dep in dependencies: + if dep.startswith("api-ms-win-crt"): + essential_dlls.append(dep) + + # Remove duplicates + essential_dlls = list(set(essential_dlls)) + + copied = 0 + skipped = 0 + missing = 0 + + for dll_name in essential_dlls: + target_path = os.path.join(target_dir, dll_name) + + # Skip if already exists + if os.path.exists(target_path): + print(f"✓ Already exists: {dll_name}") + skipped += 1 + continue + + # Find DLL in system + source_path = find_dll_in_system(dll_name) + if not source_path: + print(f"✗ NOT FOUND: {dll_name}") + missing += 1 + continue + + # Check architecture + arch = check_dll_architecture(source_path) + if arch != "ARM64": + print(f"⚠️ WARNING: {dll_name} is {arch}, not ARM64! Source: {source_path}") + continue + + # Copy the DLL + try: + shutil.copy2(source_path, target_path) + size = os.path.getsize(target_path) + print(f"✓ Copied: {dll_name} ({size:,} bytes) from {source_path}") + copied += 1 + except Exception as e: + print(f"✗ Failed to copy {dll_name}: {e}") + missing += 1 + + print(f"\n=== Summary ===") + print(f"Copied: {copied}") + print(f"Skipped (already exists): {skipped}") + print(f"Missing/Failed: {missing}") + + # Special handling for api-ms-win-crt DLLs + if missing > 0 and any(dll.startswith("api-ms-win-crt") for dll in essential_dlls): + print("\n=== Note about api-ms-win-crt DLLs ===") + print("These DLLs are part of the Universal C Runtime (UCRT).") + print("On Windows 10/11, they are typically handled by the OS.") + print("If Godot still fails to load, try:") + print("1. Installing Windows Universal C Runtime for ARM64") + print("2. Copying from: C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\arm64") + + return missing == 0 + +def main(): + """Main function.""" + import argparse + + parser = argparse.ArgumentParser(description="Copy ARM64 dependencies for py4godot") + parser.add_argument( + "--target", + default="build/final/windowsarm64/cpython-3.12.4-windowsarm64/python", + help="Target directory (default: build/final/windowsarm64/cpython-3.12.4-windowsarm64/python)" + ) + + args = parser.parse_args() + + # Convert to absolute path + target_dir = os.path.abspath(args.target) + + # Check if dumpbin is available + try: + subprocess.run(["dumpbin", "/?"], capture_output=True, check=True) + except: + print("ERROR: dumpbin.exe not found!") + print("Please run this script from a Visual Studio Developer Command Prompt") + print("or ensure Visual Studio build tools are in your PATH.") + return 1 + + # Copy dependencies + success = copy_dependencies(target_dir) + + if success: + print("\n✅ All dependencies copied successfully!") + print(f"You can now test the build in Godot.") + else: + print("\n⚠️ Some dependencies could not be copied.") + print("You may need to manually locate and copy them.") + + return 0 if success else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/arm64/copy_arm64_dependencies_simple.py b/tools/arm64/copy_arm64_dependencies_simple.py new file mode 100644 index 00000000..af7968c7 --- /dev/null +++ b/tools/arm64/copy_arm64_dependencies_simple.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +Simple ARM64 Dependencies Copier for py4godot +This version doesn't require dumpbin and copies known dependencies directly. +""" + +import os +import shutil +import sys +from pathlib import Path + +def copy_file_if_exists(source, dest, name): + """Copy a file if it exists, return True if successful.""" + if not os.path.exists(source): + print(f"✗ NOT FOUND: {name} at {source}") + return False + + if os.path.exists(dest): + print(f"✓ Already exists: {name}") + return True + + try: + shutil.copy2(source, dest) + size = os.path.getsize(dest) + print(f"✓ Copied: {name} ({size:,} bytes)") + return True + except Exception as e: + print(f"✗ Failed to copy {name}: {e}") + return False + +def find_visual_cpp_redist(): + """Find Visual C++ Redistributable DLLs.""" + search_paths = [ + r"C:\Windows\System32", + r"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.32.31332\arm64\Microsoft.VC143.CRT", + ] + + # Look for different MSVC versions + for base_path in [r"C:\Program Files\Microsoft Visual Studio\2022", + r"C:\Program Files (x86)\Microsoft Visual Studio\2022"]: + if os.path.exists(base_path): + for edition in ["Community", "Professional", "Enterprise", "BuildTools"]: + vc_path = os.path.join(base_path, edition, "VC", "Redist", "MSVC") + if os.path.exists(vc_path): + # Find all MSVC versions + for version in os.listdir(vc_path): + arm64_path = os.path.join(vc_path, version, "arm64", "Microsoft.VC143.CRT") + if os.path.exists(arm64_path): + search_paths.append(arm64_path) + + return search_paths + +def find_ucrt_dlls(): + """Find Universal C Runtime DLLs.""" + search_paths = [ + r"C:\Windows\System32", + r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\arm64", + ] + + # Find Windows Kits + kits_base = r"C:\Program Files (x86)\Windows Kits\10\Redist" + if os.path.exists(kits_base): + # Look for version-specific paths + for item in os.listdir(kits_base): + if item.startswith("10.0."): + ucrt_path = os.path.join(kits_base, item, "ucrt", "DLLs", "arm64") + if os.path.exists(ucrt_path): + search_paths.append(ucrt_path) + + return search_paths + +def copy_dependencies(target_dir): + """Copy all required dependencies to target directory.""" + print(f"=== Copying Dependencies to {target_dir} ===\n") + + if not os.path.exists(target_dir): + print(f"ERROR: Target directory does not exist: {target_dir}") + return False + + # Required DLLs from main.dll dependencies + required_dlls = [ + "python312.dll", # Already should be there + "MSVCP140.dll", # Visual C++ Runtime + "VCRUNTIME140.dll", # Visual C++ Runtime + "ucrtbase.dll", # Universal C Runtime + ] + + # Additional UCRT DLLs that might be needed + ucrt_dlls = [ + "api-ms-win-crt-heap-l1-1-0.dll", + "api-ms-win-crt-stdio-l1-1-0.dll", + "api-ms-win-crt-convert-l1-1-0.dll", + "api-ms-win-crt-runtime-l1-1-0.dll", + "api-ms-win-crt-string-l1-1-0.dll", + "api-ms-win-crt-time-l1-1-0.dll", + "api-ms-win-crt-filesystem-l1-1-0.dll", + "api-ms-win-crt-environment-l1-1-0.dll", + "api-ms-win-crt-utility-l1-1-0.dll", + "api-ms-win-crt-locale-l1-1-0.dll", + "api-ms-win-crt-math-l1-1-0.dll", + "api-ms-win-crt-multibyte-l1-1-0.dll", + "api-ms-win-crt-process-l1-1-0.dll", + ] + + # Get search paths + vc_paths = find_visual_cpp_redist() + ucrt_paths = find_ucrt_dlls() + + print("Search paths for Visual C++ Runtime:") + for path in vc_paths: + exists = "✓" if os.path.exists(path) else "✗" + print(f" {exists} {path}") + + print("\nSearch paths for UCRT:") + for path in ucrt_paths: + exists = "✓" if os.path.exists(path) else "✗" + print(f" {exists} {path}") + + print(f"\n=== Copying Visual C++ Runtime DLLs ===") + + copied = 0 + for dll in required_dlls: + dest_path = os.path.join(target_dir, dll) + found = False + + # Skip if already exists + if os.path.exists(dest_path): + print(f"✓ Already exists: {dll}") + continue + + # Search in VC paths first + for search_path in vc_paths: + source_path = os.path.join(search_path, dll) + if os.path.exists(source_path): + if copy_file_if_exists(source_path, dest_path, dll): + copied += 1 + found = True + break + + # If not found in VC paths, try system32 + if not found: + source_path = os.path.join(r"C:\Windows\System32", dll) + if copy_file_if_exists(source_path, dest_path, dll): + copied += 1 + found = True + + if not found: + print(f"✗ Could not find: {dll}") + + print(f"\n=== Copying UCRT DLLs ===") + + for dll in ucrt_dlls: + dest_path = os.path.join(target_dir, dll) + found = False + + # Skip if already exists + if os.path.exists(dest_path): + continue + + # Search in UCRT paths + for search_path in ucrt_paths: + source_path = os.path.join(search_path, dll) + if os.path.exists(source_path): + if copy_file_if_exists(source_path, dest_path, dll): + copied += 1 + found = True + break + + # Many UCRT DLLs are handled by the OS, so not finding them is OK + if not found and dll.startswith("api-ms-win-crt"): + # These are often forwarded to ucrtbase.dll by the OS + pass + + print(f"\n=== Summary ===") + print(f"Total DLLs copied: {copied}") + + # Check what we have now + print(f"\nFiles in {target_dir}:") + dll_files = list(Path(target_dir).glob("*.dll")) + for dll in sorted(dll_files): + size = dll.stat().st_size + print(f" ✓ {dll.name} ({size:,} bytes)") + + return True + +def main(): + """Main function.""" + print("=== py4godot ARM64 Dependencies Copier (Simple Version) ===\n") + + # Default target (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + target_dir = os.path.join(repo_root, "build/final/windowsarm64/cpython-3.12.4-windowsarm64/python") + + # Check if build exists + if not os.path.exists(target_dir): + print(f"Build directory not found: {target_dir}") + alt_target = input("Enter target directory path (or press Enter to exit): ").strip() + if not alt_target: + return 1 + target_dir = alt_target + + target_dir = os.path.abspath(target_dir) + + # Copy dependencies + success = copy_dependencies(target_dir) + + if success: + print("\n✅ Dependency copy process completed!") + print("\nNext steps:") + print("1. Run: python test_in_godot.py") + print("2. Test in Godot ARM64 with verbose logging") + print("3. Check console for [py4godot] messages") + + print("\nIf you still get Error 126:") + print("1. Open Visual Studio Developer Command Prompt") + print("2. Run: python copy_arm64_dependencies.py") + print("3. Use Dependencies.exe to analyze main.dll") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/arm64/create_test_project.py b/tools/arm64/create_test_project.py new file mode 100644 index 00000000..1e6b2858 --- /dev/null +++ b/tools/arm64/create_test_project.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +Create Test Project for py4godot ARM64 +This script creates a minimal Godot project to test py4godot ARM64 builds. + +Usage: python create_test_project.py [project_name] +""" + +import os +import sys +import shutil +import json +import argparse +from pathlib import Path + +def create_test_project(project_dir="test-arm64-project"): + """Create a minimal Godot project for testing py4godot ARM64.""" + print(f"=== Creating test project: {project_dir} ===\n") + + # Create project directory + os.makedirs(project_dir, exist_ok=True) + + # Create project.godot + project_content = """[application] +config/name="py4godot ARM64 Test" +config/features=PackedStringArray("4.4") + +[editor_plugins] +enabled=PackedStringArray("res://addons/py4godot/plugin.cfg") + +[rendering] +renderer/rendering_method="mobile" +""" + + with open(os.path.join(project_dir, "project.godot"), "w") as f: + f.write(project_content) + print("✓ Created project.godot") + + # Create addon directory + addon_dir = os.path.join(project_dir, "addons", "py4godot") + os.makedirs(addon_dir, exist_ok=True) + + # Copy python.gdextension (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + gdext_source = os.path.join(repo_root, "build_resources/python.gdextension") + if os.path.exists(gdext_source): + shutil.copy(gdext_source, addon_dir) + print("✓ Copied python.gdextension") + else: + print("⚠️ python.gdextension not found!") + + # Create plugin.cfg + plugin_content = """[plugin] + +name="py4godot" +description="Python language support for Godot 4" +author="Niklas Heer" +version="4.4.1" +script="plugin.gd" +""" + + with open(os.path.join(addon_dir, "plugin.cfg"), "w") as f: + f.write(plugin_content) + print("✓ Created plugin.cfg") + + # Create minimal plugin.gd + plugin_gd = """@tool +extends EditorPlugin + +func _enter_tree(): + print("[py4godot] Plugin entering tree") + pass + +func _exit_tree(): + print("[py4godot] Plugin exiting tree") + pass +""" + + with open(os.path.join(addon_dir, "plugin.gd"), "w") as f: + f.write(plugin_gd) + print("✓ Created plugin.gd") + + return project_dir, addon_dir + +def copy_build_to_project(addon_dir): + """Copy the ARM64 build to the test project.""" + print("\n=== Copying ARM64 build ===\n") + + # Source directory (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + source_dir = os.path.join(repo_root, "build/final/windowsarm64/cpython-3.12.4-windowsarm64") + if not os.path.exists(source_dir): + print(f"✗ Build not found at: {source_dir}") + print("Please run: python build.py --target_platform=windowsarm64 --compiler=msvc") + return False + + # Destination + dest_dir = os.path.join(addon_dir, "cpython-3.12.4-windowsarm64") + + # Remove old build if exists + if os.path.exists(dest_dir): + print(f"Removing old build at: {dest_dir}") + shutil.rmtree(dest_dir) + + # Copy new build + print(f"Copying from: {source_dir}") + print(f"Copying to: {dest_dir}") + shutil.copytree(source_dir, dest_dir) + + # Count files + dll_count = len(list(Path(dest_dir).rglob("*.dll"))) + print(f"✓ Copied build ({dll_count} DLL files)") + + return True + +def create_test_script(project_dir): + """Create a test Python script.""" + test_script = """from py4godot import * +from py4godot.classes.Node import * + +@gdclass +class TestNode(Node): + def _ready(self): + print("[py4godot] Hello from Python on ARM64!") + print(f"[py4godot] Python node name: {self.name}") + + def _process(self, delta): + pass +""" + + script_path = os.path.join(project_dir, "test_node.py") + with open(script_path, "w") as f: + f.write(test_script) + print(f"✓ Created test script: test_node.py") + + # Create test scene + scene_content = """[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://test_node.py" id="1"] + +[node name="TestNode" type="Node"] +script = ExtResource("1") +""" + + scene_path = os.path.join(project_dir, "test_scene.tscn") + with open(scene_path, "w") as f: + f.write(scene_content) + print(f"✓ Created test scene: test_scene.tscn") + +def create_debug_batch(project_dir): + """Create a batch file for debugging.""" + batch_content = """@echo off +echo === py4godot ARM64 Debug Launch === +echo. +echo Starting Godot with verbose logging... +echo Check the console output for [py4godot] messages +echo. + +set GODOT_PATH=Godot_v4.4.1-stable_windows_arm64.exe + +if not exist "%GODOT_PATH%" ( + echo ERROR: Godot executable not found! + echo Please download Godot ARM64 and place it in this directory + echo Download from: https://godotengine.org/download/windows/ + pause + exit /b 1 +) + +echo Running: %GODOT_PATH% --verbose --path . --editor +%GODOT_PATH% --verbose --path . --editor + +echo. +echo === Godot closed === +echo Check above for any [py4godot] error messages +pause +""" + + batch_path = os.path.join(project_dir, "debug_godot.bat") + with open(batch_path, "w") as f: + f.write(batch_content) + print(f"✓ Created debug batch file: debug_godot.bat") + +def main(): + """Main function.""" + # Parse command line arguments + parser = argparse.ArgumentParser( + description="Create a minimal Godot project to test py4godot ARM64 builds" + ) + parser.add_argument( + "project_name", + nargs="?", + default="test-arm64-project", + help="Name of the test project directory (default: test-arm64-project)" + ) + args = parser.parse_args() + + print("=== py4godot ARM64 Test Project Creator ===\n") + + # Create test project + project_dir, addon_dir = create_test_project(args.project_name) + + # Copy build + if not copy_build_to_project(addon_dir): + return 1 + + # Create test files + create_test_script(project_dir) + create_debug_batch(project_dir) + + # Instructions + print("\n=== Setup Complete! ===\n") + print(f"Test project created at: {os.path.abspath(project_dir)}") + print("\nNext steps:") + print("1. Copy Godot ARM64 executable to the test project directory") + print(" Download from: https://godotengine.org/download/windows/") + print(" Rename to: Godot_v4.4.1-stable_windows_arm64.exe") + print("\n2. Run the debug batch file:") + print(f" cd {project_dir}") + print(" debug_godot.bat") + print("\n3. In Godot:") + print(" - Check Project Settings > Plugins > py4godot is enabled") + print(" - Open test_scene.tscn") + print(" - Run the scene") + print("\n4. Check console output for:") + print(" - [py4godot] log messages") + print(" - Any error messages about DLL loading") + print("\nExpected output if working:") + print(" [py4godot] Plugin entering tree") + print(" [py4godot] Hello from Python on ARM64!") + + # Check for dependencies + python_dir = os.path.join(addon_dir, "cpython-3.12.4-windowsarm64", "python") + missing_deps = [] + for dll in ["msvcp140.dll", "ucrtbase.dll"]: + if not os.path.exists(os.path.join(python_dir, dll)): + missing_deps.append(dll) + + if missing_deps: + print(f"\n⚠️ WARNING: Missing dependencies: {', '.join(missing_deps)}") + print("Run these commands first:") + print(" python tools/arm64/copy_arm64_dependencies_simple.py") + print(" python tools/arm64/find_ucrt_dlls.py") + + return 0 + +if __name__ == "__main__": + import sys + sys.exit(main()) \ No newline at end of file diff --git a/tools/arm64/find_ucrt_dlls.py b/tools/arm64/find_ucrt_dlls.py new file mode 100644 index 00000000..90ad93b5 --- /dev/null +++ b/tools/arm64/find_ucrt_dlls.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +Find and copy UCRT (Universal C Runtime) DLLs for ARM64 +""" + +import os +import glob +import shutil +from pathlib import Path + +def find_windows_kits(): + """Find all Windows Kits installations.""" + kits = [] + + # Common locations for Windows Kits + base_paths = [ + r"C:\Program Files (x86)\Windows Kits\10", + r"C:\Program Files\Windows Kits\10", + ] + + for base in base_paths: + if os.path.exists(base): + # Look for Redist folders + redist_pattern = os.path.join(base, "Redist", "*", "ucrt", "DLLs", "arm64") + for path in glob.glob(redist_pattern): + if os.path.exists(path): + kits.append(path) + + # Also check direct ucrt path + ucrt_path = os.path.join(base, "Redist", "ucrt", "DLLs", "arm64") + if os.path.exists(ucrt_path) and ucrt_path not in kits: + kits.append(ucrt_path) + + return kits + +def list_ucrt_dlls(kit_path): + """List all UCRT DLLs in a Windows Kit path.""" + dlls = [] + pattern = os.path.join(kit_path, "api-ms-win-crt-*.dll") + for dll in glob.glob(pattern): + dlls.append(os.path.basename(dll)) + + # Also include ucrtbase.dll if present + ucrtbase = os.path.join(kit_path, "ucrtbase.dll") + if os.path.exists(ucrtbase): + dlls.append("ucrtbase.dll") + + return sorted(dlls) + +def copy_ucrt_dlls(source_dir, target_dir): + """Copy all UCRT DLLs from source to target.""" + if not os.path.exists(target_dir): + print(f"ERROR: Target directory does not exist: {target_dir}") + return False + + dlls = list_ucrt_dlls(source_dir) + copied = 0 + + print(f"\nCopying from: {source_dir}") + print(f"Copying to: {target_dir}\n") + + for dll in dlls: + source = os.path.join(source_dir, dll) + target = os.path.join(target_dir, dll) + + if os.path.exists(target): + print(f"✓ Already exists: {dll}") + continue + + try: + shutil.copy2(source, target) + size = os.path.getsize(target) + print(f"✓ Copied: {dll} ({size:,} bytes)") + copied += 1 + except Exception as e: + print(f"✗ Failed to copy {dll}: {e}") + + return copied + +def main(): + """Main function.""" + print("=== Windows ARM64 UCRT DLL Finder ===\n") + + # Find Windows Kits + print("Searching for Windows Kits installations...") + kits = find_windows_kits() + + if not kits: + print("\n✗ No Windows Kits with ARM64 UCRT found!") + print("\nPlease install Windows SDK with ARM64 support:") + print("https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/") + return 1 + + print(f"\nFound {len(kits)} Windows Kit(s) with ARM64 UCRT:") + for i, kit in enumerate(kits): + dlls = list_ucrt_dlls(kit) + print(f"\n{i+1}. {kit}") + print(f" Contains {len(dlls)} UCRT DLLs") + if len(dlls) > 0: + print(f" Including: {', '.join(dlls[:5])}{'...' if len(dlls) > 5 else ''}") + + # Default target directory (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + target_dir = os.path.join(repo_root, "build/final/windowsarm64/cpython-3.12.4-windowsarm64/python") + + if not os.path.exists(target_dir): + print(f"\n⚠️ Default target directory not found: {target_dir}") + target_dir = input("Enter target directory path: ").strip() + if not target_dir: + return 1 + + target_dir = os.path.abspath(target_dir) + + # If only one kit, use it automatically + if len(kits) == 1: + source_kit = kits[0] + else: + # Let user choose + print("\nWhich Windows Kit to use?") + choice = input(f"Enter number (1-{len(kits)}): ").strip() + try: + idx = int(choice) - 1 + if 0 <= idx < len(kits): + source_kit = kits[idx] + else: + print("Invalid choice!") + return 1 + except: + print("Invalid input!") + return 1 + + # Copy DLLs + print(f"\n=== Copying UCRT DLLs ===") + copied = copy_ucrt_dlls(source_kit, target_dir) + + if copied > 0: + print(f"\n✅ Successfully copied {copied} DLL(s)!") + print("\nNext steps:") + print("1. Copy any remaining dependencies with: python copy_arm64_dependencies.py") + print("2. Test in Godot with the verbose logging") + else: + print("\n⚠️ No new DLLs were copied (they may already exist)") + + # Also show manual copy command + print(f"\nManual copy command (PowerShell):") + print(f'Copy-Item "{source_kit}\\*.dll" -Destination "{target_dir}" -Force') + + return 0 + +if __name__ == "__main__": + import sys + sys.exit(main()) \ No newline at end of file diff --git a/tools/arm64/test_arm64_loading.py b/tools/arm64/test_arm64_loading.py new file mode 100644 index 00000000..c594df51 --- /dev/null +++ b/tools/arm64/test_arm64_loading.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +Test ARM64 DLL Loading +This script tests if we can load the main.dll directly using ctypes. +""" + +import os +import sys +import ctypes +from ctypes import wintypes + +def test_load_library(dll_path): + """Test loading a DLL and report detailed errors.""" + print(f"\nTesting: {dll_path}") + + if not os.path.exists(dll_path): + print(f"ERROR: File does not exist!") + return False + + # Get absolute path + abs_path = os.path.abspath(dll_path) + print(f"Absolute path: {abs_path}") + + # Try to load with ctypes + try: + # First, try setting the DLL directory + kernel32 = ctypes.windll.kernel32 + kernel32.SetDllDirectoryW.argtypes = [wintypes.LPCWSTR] + kernel32.SetDllDirectoryW.restype = wintypes.BOOL + + dll_dir = os.path.dirname(abs_path) + if kernel32.SetDllDirectoryW(dll_dir): + print(f"Set DLL directory to: {dll_dir}") + + # Try loading the library + print("Attempting to load library...") + lib = ctypes.CDLL(abs_path) + print("✓ Library loaded successfully!") + + # Try to find py4godot_init + try: + py4godot_init = lib.py4godot_init + print("✓ Found py4godot_init symbol!") + return True + except AttributeError: + print("✗ py4godot_init symbol not found!") + # List all available symbols if possible + return False + + except OSError as e: + print(f"✗ Failed to load library: {e}") + + # Get more detailed error info + error_code = ctypes.get_last_error() + if error_code: + print(f"Windows error code: {error_code}") + + # Get error message + kernel32 = ctypes.windll.kernel32 + local_free = kernel32.LocalFree + get_last_error = kernel32.GetLastError + format_message = kernel32.FormatMessageW + + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 + FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 + FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 + + message_buffer = ctypes.c_wchar_p() + format_message( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + None, + error_code, + 0, + ctypes.byref(message_buffer), + 0, + None + ) + + if message_buffer.value: + print(f"Error details: {message_buffer.value}") + local_free(message_buffer) + + return False + +def check_python_path(): + """Check if Python can find necessary modules.""" + print("\n=== Python Environment Check ===") + print(f"Python version: {sys.version}") + print(f"Python executable: {sys.executable}") + print(f"Python path:") + for p in sys.path: + print(f" - {p}") + +def main(): + print("=== py4godot ARM64 DLL Loading Test ===\n") + + # Check Python environment + check_python_path() + + # Define paths to test (relative to repository root) + repo_root = os.path.join(os.path.dirname(__file__), "..", "..") + base_path = os.path.join(repo_root, "build/final/windowsarm64/cpython-3.12.4-windowsarm64/python") + + if not os.path.exists(base_path): + # Try alternative path from test project + alt_path = os.path.join(repo_root, "addons/py4godot/cpython-3.12.4-windowsarm64/python") + if os.path.exists(alt_path): + base_path = alt_path + else: + print(f"ERROR: Build directory not found. Tried:") + print(f" - {base_path}") + print(f" - {alt_path}") + print("Please run the build first from repository root:") + print("python build.py --target_platform=windowsarm64 --compiler=msvc") + return 1 + + print(f"\nUsing base path: {base_path}") + + # Test loading pythonscript.dll first (should work) + pythonscript_path = os.path.join(base_path, "pythonscript.dll") + if os.path.exists(pythonscript_path): + test_load_library(pythonscript_path) + + # Test loading main.dll (this is where it fails) + main_dll_path = os.path.join(base_path, "main.dll") + if os.path.exists(main_dll_path): + success = test_load_library(main_dll_path) + + if not success: + print("\n=== Diagnostic Information ===") + print("The main.dll exists but cannot be loaded. Common causes:") + print("1. Missing dependencies (use Dependency Walker to check)") + print("2. Architecture mismatch (x64 dependencies on ARM64 system)") + print("3. Missing Visual C++ Redistributables for ARM64") + print("4. Symbol export issues (py4godot_init not properly exported)") + print("\nNext steps:") + print("- Run check_arm64_dependencies.py for detailed analysis") + print("- Use Dependency Walker on main.dll") + print("- Check Event Viewer for additional error details") + else: + print(f"\nERROR: main.dll not found at: {main_dll_path}") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file