From 34fb49dc7918d84a4469aa8796aab25633a80e23 Mon Sep 17 00:00:00 2001 From: gr3vios Date: Sun, 27 Apr 2025 09:35:05 -0600 Subject: [PATCH 1/4] This commit introduces support for Windows ARM64 builds in the py4godot project. Changes include: - Adjustments to the build scripts to handle Windows ARM64 architecture. - Dependencies updated to align with the requirements for this platform. Known limitations: - This implementation depends on Python 3.12.4 for Windows ARM64, which is not yet available. - As a temporary workaround, Python 3.9.7 from jay0lee/CPython-Windows-ARM64 has been considered. This work follows the same approach as the Linux ARM64 support and is a draft implementation. Feedback is welcome, especially regarding compatibility and potential improvements. --- meson_scripts/copy_tools.py | 3 +- meson_scripts/download_python.py | 2 +- meson_scripts/platform_check.py | 15 +++++++-- platforms/windowsarm64.cross | 13 ++++++++ py4godot/godot_bindings/main.h | 6 +++- py4godot/godot_bindings/pythonscript.cpp | 42 ++++++++++++++++++++---- 6 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 platforms/windowsarm64.cross diff --git a/meson_scripts/copy_tools.py b/meson_scripts/copy_tools.py index 186e98ed..fd0eacfc 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("linux64").lstrip("windows64").lstrip("windows32").lstrip("linux32").lstrip("darwin64") - + return text.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 f8e8eaf9..faa43bb6 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":"armv7-unknown-linux-gnueabi-install_only_stripped"} + "linuxarm64":"armv7-unknown-linux-gnueabi-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" diff --git a/meson_scripts/platform_check.py b/meson_scripts/platform_check.py index 0ec87f20..af5da7ce 100644 --- a/meson_scripts/platform_check.py +++ b/meson_scripts/platform_check.py @@ -2,5 +2,16 @@ import platform def get_platform(): - """Determining the current platform""" - return platform.system().lower()+("64" if struct.calcsize("P")*8 == 64 else "") + """Return 'windows64' for Windows x86_64, 'windowsarm64' for Windows ARM64""" + system = platform.system().lower() + arch = platform.machine().lower() + + 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..bd16b8a2 --- /dev/null +++ b/platforms/windowsarm64.cross @@ -0,0 +1,13 @@ +[binaries] +ar = 'ar' +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.h b/py4godot/godot_bindings/main.h index 4e50a38d..ab68e8f8 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 c9dcec42..6161b77c 100644 --- a/py4godot/godot_bindings/pythonscript.cpp +++ b/py4godot/godot_bindings/pythonscript.cpp @@ -4,6 +4,9 @@ #if defined(__linux__) || defined(__APPLE__) #include #include // For dlopen, dlsym, dlclose on Linux +#elif defined(_WIN32) || defined(_WIN64) +#include +#include // For LoadLibrary, GetProcAddress, FreeLibrary on Windows #endif typedef GDExtensionBool (*Py4GodotInitFunc)(GDExtensionInterfaceGetProcAddress p_get_proc_address, @@ -54,13 +57,40 @@ extern "C" { 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; + #if defined(_WIN32) || defined(_WIN64) + // Dynamic loading on Windows + HMODULE handle = nullptr; + + #if defined(_M_ARM64) + // Load the ARM64 Windows DLL + handle = LoadLibraryA("addons\\py4godot\\cpython-3.12.4-windowsarm64\\python\\main.dll"); + #else + // Load the x86_64 Windows DLL + handle = LoadLibraryA("addons\\py4godot\\cpython-3.12.4-windows64\\python\\main.dll"); #endif - } -} + if (!handle) { + std::cerr << "Cannot load library: " << GetLastError() << std::endl; + return 1; + } + + // Load the py4godot_init symbol + Py4GodotInitFunc load_function_handle = (Py4GodotInitFunc)GetProcAddress(handle, "py4godot_init"); + + if (!load_function_handle) { + std::cerr << "Cannot load symbol 'py4godot_init': " << GetLastError() << std::endl; + FreeLibrary(handle); // Free the library before exiting + return 1; + } + + // Call the loaded function + int result = load_function_handle(p_get_proc_address, p_library, r_initialization); + // Don't free the library as it needs to stay loaded + // FreeLibrary(handle); + return result; + #endif + + } +} \ No newline at end of file From 3e65e168930935dc541b95ff95d545d0b7a47fbf Mon Sep 17 00:00:00 2001 From: gr3vios Date: Sun, 27 Apr 2025 10:21:30 -0600 Subject: [PATCH 2/4] Refactor library loading logic to support Windows ARM64 and improve cross-platform compatibility --- py4godot/godot_bindings/pythonscript.cpp | 76 ++++++++++-------------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/py4godot/godot_bindings/pythonscript.cpp b/py4godot/godot_bindings/pythonscript.cpp index 6161b77c..993c94c0 100644 --- a/py4godot/godot_bindings/pythonscript.cpp +++ b/py4godot/godot_bindings/pythonscript.cpp @@ -3,7 +3,7 @@ #if defined(__linux__) || defined(__APPLE__) #include -#include // For dlopen, dlsym, dlclose on Linux +#include // For dlopen, dlsym, dlclose on Linux/macOS #elif defined(_WIN32) || defined(_WIN64) #include #include // For LoadLibrary, GetProcAddress, FreeLibrary on Windows @@ -13,84 +13,72 @@ typedef GDExtensionBool (*Py4GodotInitFunc)(GDExtensionInterfaceGetProcAddress p 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) { - - #if defined(__linux__) - // Load the shared library on Linux - void* handle = dlopen("addons/py4godot/cpython-3.12.4-linux64/python/bin/main.so", RTLD_NOW | RTLD_GLOBAL); - #elif defined(__APPLE__) - // Load the shared library on macOS - void* handle = dlopen("addons/py4godot/cpython-3.12.4-darwin64/python/bin/main.dylib", RTLD_NOW | RTLD_GLOBAL); - #endif + std::string library_path = get_library_path(); + if (library_path.empty()) { + std::cerr << "Unsupported platform or architecture." << std::endl; + return 1; + } #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 - #if defined(_WIN32) || defined(_WIN64) - // Dynamic loading on Windows - HMODULE handle = nullptr; - - #if defined(_M_ARM64) - // Load the ARM64 Windows DLL - handle = LoadLibraryA("addons\\py4godot\\cpython-3.12.4-windowsarm64\\python\\main.dll"); - #else - // Load the x86_64 Windows DLL - handle = LoadLibraryA("addons\\py4godot\\cpython-3.12.4-windows64\\python\\main.dll"); - #endif - + #elif defined(_WIN32) || defined(_WIN64) + HMODULE handle = LoadLibraryA(library_path.c_str()); if (!handle) { std::cerr << "Cannot load library: " << GetLastError() << std::endl; return 1; } - // Load the py4godot_init symbol Py4GodotInitFunc load_function_handle = (Py4GodotInitFunc)GetProcAddress(handle, "py4godot_init"); - if (!load_function_handle) { std::cerr << "Cannot load symbol 'py4godot_init': " << GetLastError() << std::endl; - FreeLibrary(handle); // Free the library before exiting + FreeLibrary(handle); return 1; } - // Call the loaded function int result = load_function_handle(p_get_proc_address, p_library, r_initialization); - - // Don't free the library as it needs to stay loaded - // FreeLibrary(handle); - + // Keep the library loaded for the application's lifetime return result; #endif - } } \ No newline at end of file From 98edac9b5b34b2f3b6e39600a08a5dd692c97e1b Mon Sep 17 00:00:00 2001 From: grevios Date: Wed, 11 Jun 2025 09:03:59 -0600 Subject: [PATCH 3/4] Use prebuilt-windowsarm64 repo and fix MSVC ARM64 - Update download_file() to use niklas2902/prebuilt-windowsarm64 repository - Add Windows ARM64 entries to python.gdextension - Fix MSVC initialization to use x64_arm64 for Windows ARM64 builds --- build.py | 6 +++++- build_resources/python.gdextension | 2 ++ meson_scripts/download_python.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index ce486531..2f53a7da 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: diff --git a/build_resources/python.gdextension b/build_resources/python.gdextension index a923000c..d11a7fd7 100644 --- a/build_resources/python.gdextension +++ b/build_resources/python.gdextension @@ -8,6 +8,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_scripts/download_python.py b/meson_scripts/download_python.py index faa43bb6..6b7bdac1 100644 --- a/meson_scripts/download_python.py +++ b/meson_scripts/download_python.py @@ -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' From c137bb084b984ac7ee1780bfdf56b19077cb66ad Mon Sep 17 00:00:00 2001 From: grevios Date: Fri, 13 Jun 2025 17:42:33 -0600 Subject: [PATCH 4/4] Complete Windows ARM64 support implementation with diagnostic tools Implement comprehensive Windows ARM64 support for py4godot based on extensive testing and debugging of compiled plugin builds. Core ARM64 implementation: - Add symbol export fix via py4godot/godot_bindings/main.def for proper DLL initialization - Update meson.build to use module definition file for Windows builds - Fix ARM64 library loading in pythonscript.cpp with path corrections and enhanced logging - Add ARM64 platform configuration in platforms/windowsarm64.cross - Update build_resources/plugin.cfg and python.gdextension for ARM64 compatibility Key technical fixes: - Resolve Error 126 library loading failures through dependency management - Ensure py4godot_init symbol is properly exported from main.dll - Add ARM64-specific path handling and library resolution - Configure MSVC cross-compilation settings for ARM64 target Testing and diagnostic infrastructure: Based on extensive testing of compiled ARM64 builds, create comprehensive diagnostic tools: - Developer tools in tools/arm64/ for build verification and troubleshooting - End-user tools in build_resources/plugin_tools/ for plugin distribution - Automated test project creation and dependency verification utilities Build system enhancements: - Support windowsarm64 platform in cross-compilation configuration - Integrate ARM64 dependency detection and copying workflows - Add plugin configuration updates for ARM64 library paths These changes result from thorough testing of ARM64 compiled builds and resolve all identified compatibility issues, enabling native py4godot execution on Windows ARM64 devices. --- build.py | 1 + build_resources/plugin.cfg | 7 + build_resources/plugin_tools/README.md | 164 ++++++++++++ .../diagnostic/check_dependencies.py | 232 ++++++++++++++++ .../plugin_tools/diagnostic/test_loading.py | 184 +++++++++++++ .../plugin_tools/setup/copy_dependencies.py | 226 ++++++++++++++++ meson.build | 14 +- platforms/windowsarm64.cross | 2 +- py4godot/godot_bindings/main.def | 3 + py4godot/godot_bindings/pythonscript.cpp | 81 +++++- tools/arm64/check_arm64_dependencies.py | 216 +++++++++++++++ tools/arm64/copy_arm64_dependencies.py | 217 +++++++++++++++ tools/arm64/copy_arm64_dependencies_simple.py | 228 ++++++++++++++++ tools/arm64/create_test_project.py | 248 ++++++++++++++++++ tools/arm64/find_ucrt_dlls.py | 152 +++++++++++ tools/arm64/test_arm64_loading.py | 146 +++++++++++ 16 files changed, 2115 insertions(+), 6 deletions(-) create mode 100644 build_resources/plugin.cfg create mode 100644 build_resources/plugin_tools/README.md create mode 100644 build_resources/plugin_tools/diagnostic/check_dependencies.py create mode 100644 build_resources/plugin_tools/diagnostic/test_loading.py create mode 100644 build_resources/plugin_tools/setup/copy_dependencies.py create mode 100644 py4godot/godot_bindings/main.def create mode 100644 tools/arm64/check_arm64_dependencies.py create mode 100644 tools/arm64/copy_arm64_dependencies.py create mode 100644 tools/arm64/copy_arm64_dependencies_simple.py create mode 100644 tools/arm64/create_test_project.py create mode 100644 tools/arm64/find_ucrt_dlls.py create mode 100644 tools/arm64/test_arm64_loading.py diff --git a/build.py b/build.py index 2f53a7da..b77d95a5 100644 --- a/build.py +++ b/build.py @@ -220,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/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/platforms/windowsarm64.cross b/platforms/windowsarm64.cross index bd16b8a2..8e5e4160 100644 --- a/platforms/windowsarm64.cross +++ b/platforms/windowsarm64.cross @@ -1,5 +1,5 @@ [binaries] -ar = 'ar' + ar = 'lib' strip = 'strip' exe_wrapper = '' # Empty unless you need to run ARM64 binaries on a different host. 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/pythonscript.cpp b/py4godot/godot_bindings/pythonscript.cpp index c09bc942..e344bfa8 100644 --- a/py4godot/godot_bindings/pythonscript.cpp +++ b/py4godot/godot_bindings/pythonscript.cpp @@ -21,9 +21,9 @@ std::string get_library_path() { 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"; + return "addons/py4godot/cpython-3.12.4-windowsarm64/python/main.dll"; #else - return "addons\\py4godot\\cpython-3.12.4-windows64\\python\\main.dll"; + return "addons/py4godot/cpython-3.12.4-windows64/python/main.dll"; #endif #else return ""; // Unsupported platform @@ -80,15 +80,88 @@ extern "C" { return result; #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) { - std::cerr << "Cannot load library: " << GetLastError() << std::endl; + 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) { - std::cerr << "Cannot load symbol 'py4godot_init': " << GetLastError() << std::endl; + 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; } 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