diff --git a/.gitignore b/.gitignore index 260f51b0..75162311 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /build-ios/ /build-android/ /build-classic/ +/build-port-gate/ /cmake-build-*/ compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eb50c5f..48ca24e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(MTGODOT_BUILD_EXTENSION "Build the Godot GDExtension (pulls in godot-cpp)" ON) option(MTGODOT_BUILD_TOOLS "Build libgr2 validation tools (gr2dump/gr2fuzz/oracle_diff)" OFF) option(MTGODOT_BUILD_NET_TOOLS "Build host-only protocol probes without Godot" OFF) +option(MTGODOT_BUILD_PORT "Build only port_logic (with MTGODOT_BUILD_EXTENSION=OFF), e.g. for Linux/Windows" OFF) # --- shared, cross-platform core (no godot-cpp, no third-party) --- add_subdirectory(libgr2) @@ -71,3 +72,9 @@ if(MTGODOT_BUILD_NET_TOOLS AND NOT MTGODOT_BUILD_EXTENSION) add_executable(net_classic_e2e_host extension/tools/net_classic_e2e.cpp) target_link_libraries(net_classic_e2e_host PRIVATE mtnet_host) endif() + +# port_logic has no Godot dependency. On targets without an extension build (Linux, Windows) it is +# built on its own so the 2A gate (header self-containment, static_asserts) runs per platform. +if(MTGODOT_BUILD_PORT AND NOT MTGODOT_BUILD_EXTENSION) + add_subdirectory(extension/src/port) +endif() diff --git a/docs/PORT-PLAN.md b/docs/PORT-PLAN.md index d3af876e..8e76094a 100644 --- a/docs/PORT-PLAN.md +++ b/docs/PORT-PLAN.md @@ -119,6 +119,26 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次 `ThingInstance.h`/`GrpBase.h` 等);P0 单元里 `InstanceBase`、`PythonCharacterManager`、`PythonPlayer`、 `PythonPlayerEventHandler` 的头文件互相包含,必须作为一组串行移植,`ActorInstance.cpp` 排在最前。 +2A 当前进度(`script/port_gate.sh` 复现;构建目录 `build-port-gate/`): + +- 已完成:`port/common/`(`Win32Types.h`、`Win32Crt.{h,cpp}`(MSVC `_snprintf` 截断语义、`timeGetTime` 32 位回绕)、 + `StdAfx.h`);`port_logic` 目标已链接进 libmtgodot,也可用 `-DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_PORT=ON` + 单独构建;每个 `port/**.h` 独立编译两次的门禁 `port_header_gate`;`port.common` 测试。编译宏取 40250 发布版 + `Metin2Distribute.exe` 的 `Distribute|Win32` 配置:所有库 `NDEBUG`,GameLib 加 `USE_LOD`,UserInterface 加 + `USE_LOD;_DISTRIBUTE`;不定义 `WIN32`。 +- 未完成:D3DX 数学层、闭包头文件照抄(步骤 2)、platform 接口骨架(步骤 4)。 + +| 平台 | port_gate | +| --- | --- | +| macOS arm64 | PASS(含 `port.common` 测试) | +| Android arm64(NDK 27.2,API 24) | PASS | +| iOS arm64 | PASS(Xcode,未签名) | +| Windows x64 | mingw-w64 PASS;MSVC 需在 Windows 主机上跑,**BLOCKED** | +| Linux x86_64 | **BLOCKED**(本机无 Linux 工具链;Linux 主机上直接运行脚本) | + +已知的无关问题:完整 Android 扩展构建在 `extension/src/net/text_codec.cpp` 失败(bionic 从 API 28 才声明 `iconv`, +而构建用 API 24;c9389431 引入);仓库里的 `build-android/` 缓存还指向旧路径 `mtgodot-poc`。 + ### 批次 2R:资源包能力盘点 40250 的读取链不只是 `EterPack.cpp`,还有 `EterPackManager.cpp`(多包覆盖顺序、路径归一化)、 diff --git a/extension/CMakeLists.txt b/extension/CMakeLists.txt index 3b1b15a0..4b38513c 100644 --- a/extension/CMakeLists.txt +++ b/extension/CMakeLists.txt @@ -56,6 +56,9 @@ target_include_directories(mtproto PUBLIC src/proto) target_link_libraries(mtproto PUBLIC mt3p::sodium mt3p::minilzo) target_compile_features(mtproto PUBLIC cxx_std_20) +# --- port_logic: 40250 logic mirror (extension/src/port, docs/PORT-PLAN.md) --- +add_subdirectory(src/port) + # Host-only CLIs + tests: they run on the build machine, so skip them entirely # when cross-compiling the extension for a device. set(MT_HOST_BUILD FALSE) @@ -186,7 +189,7 @@ add_library(mtgodot ${MT_LIB_KIND} src/proto/proto_node.cpp ) target_compile_features(mtgodot PRIVATE cxx_std_20) -target_link_libraries(mtgodot PRIVATE godot::cpp xrender::libgr2 xrender::formats mtnet mtproto) +target_link_libraries(mtgodot PRIVATE godot::cpp xrender::libgr2 xrender::formats mtnet mtproto port_logic) # Godot's .gdextension expects, per platform: # project/bin/libmtgodot.macos.template_{debug,release}.dylib diff --git a/extension/src/port/CMakeLists.txt b/extension/src/port/CMakeLists.txt new file mode 100644 index 00000000..ebfd7311 --- /dev/null +++ b/extension/src/port/CMakeLists.txt @@ -0,0 +1,65 @@ +# port_logic — mirror of the 40250 client logic (extension/src/port//.{h,cpp}). +# No godot-cpp dependency: libmtgodot links it, and it also builds on its own (top-level +# -DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_PORT=ON) for targets without an extension build. +# See docs/PORT-PLAN.md batch 2A. + +file(GLOB_RECURSE MT_PORT_SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +file(GLOB_RECURSE MT_PORT_HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) + +add_library(port_logic STATIC ${MT_PORT_SOURCES}) +target_include_directories(port_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(port_logic PUBLIC cxx_std_20) +if(WIN32) + target_link_libraries(port_logic PUBLIC winmm) +endif() + +# 40250 Distribute|Win32 preprocessor definitions per library (vs_files//.vcxproj), +# minus WIN32/_WINDOWS/_LIB/_CRT_SECURE_NO_WARNINGS: Windows-only branches go through platform/, +# never through ported logic. UserInterface/Locale_inc.h feature macros come with that header. +set(MT_PORT_DEFINES_default NDEBUG) +set(MT_PORT_DEFINES_GameLib NDEBUG USE_LOD) +set(MT_PORT_DEFINES_UserInterface NDEBUG USE_LOD _DISTRIBUTE) + +function(mt_port_apply_defines file) + file(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${file}) + string(REGEX REPLACE "/.*" "" lib "${rel}") + if(DEFINED MT_PORT_DEFINES_${lib}) + set(defs ${MT_PORT_DEFINES_${lib}}) + else() + set(defs ${MT_PORT_DEFINES_default}) + endif() + set_property(SOURCE ${file} APPEND PROPERTY COMPILE_DEFINITIONS ${defs}) +endfunction() + +foreach(src IN LISTS MT_PORT_SOURCES) + mt_port_apply_defines(${src}) +endforeach() + +# Gate: every header under port/ compiles on its own (and twice, for its include guard), with its +# library's definitions. Built by default, so every platform build enforces it. +set(MT_PORT_GATE_SOURCES) +foreach(hdr IN LISTS MT_PORT_HEADERS) + file(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${hdr}) + string(MAKE_C_IDENTIFIER "${rel}" id) + set(tu ${CMAKE_CURRENT_BINARY_DIR}/header_gate/${id}.cpp) + file(CONFIGURE OUTPUT ${tu} CONTENT "#include \"${rel}\"\n#include \"${rel}\"\n") + set_property(SOURCE ${tu} APPEND PROPERTY OBJECT_DEPENDS ${hdr}) + list(APPEND MT_PORT_GATE_SOURCES ${tu}) + # Definitions follow the header's library, not the generated file's location. + string(REGEX REPLACE "/.*" "" lib "${rel}") + if(DEFINED MT_PORT_DEFINES_${lib}) + set_property(SOURCE ${tu} APPEND PROPERTY COMPILE_DEFINITIONS ${MT_PORT_DEFINES_${lib}}) + else() + set_property(SOURCE ${tu} APPEND PROPERTY COMPILE_DEFINITIONS ${MT_PORT_DEFINES_default}) + endif() +endforeach() +add_library(port_header_gate OBJECT ${MT_PORT_GATE_SOURCES}) +target_include_directories(port_header_gate PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(port_header_gate PRIVATE cxx_std_20) +add_dependencies(port_logic port_header_gate) + +if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) + add_executable(port_common_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_common_test.cpp) + target_link_libraries(port_common_test PRIVATE port_logic) + add_test(NAME port.common COMMAND $) +endif() diff --git a/extension/src/port/common/StdAfx.h b/extension/src/port/common/StdAfx.h new file mode 100644 index 00000000..993718a3 --- /dev/null +++ b/extension/src/port/common/StdAfx.h @@ -0,0 +1,19 @@ +#pragma once +// Base of every ported library's slim StdAfx.h. 40250's EterBase/StdAfx.h pulls in +// and the STL headers below; the Windows part is replaced by Win32Types.h/Win32Crt.h. A library's +// own StdAfx.h adds only the headers audit/slices/*.json lists under `stdafx_supplied` for it. + +#include "Win32Types.h" +#include "Win32Crt.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include diff --git a/extension/src/port/common/Win32Crt.cpp b/extension/src/port/common/Win32Crt.cpp new file mode 100644 index 00000000..17de39a1 --- /dev/null +++ b/extension/src/port/common/Win32Crt.cpp @@ -0,0 +1,63 @@ +#include "Win32Crt.h" + +#if !defined(_WIN32) +#include +#include +#include +#include + +char* _strlwr(char* s) +{ + for (char* p = s; *p; ++p) + *p = static_cast(std::tolower(static_cast(*p))); + return s; +} + +char* _strupr(char* s) +{ + for (char* p = s; *p; ++p) + *p = static_cast(std::toupper(static_cast(*p))); + return s; +} + +int _vsnprintf(char* buf, size_t count, const char* fmt, va_list ap) +{ + va_list probe; + va_copy(probe, ap); + const int len = std::vsnprintf(nullptr, 0, fmt, probe); + va_end(probe); + if (len < 0) + return -1; + + const size_t need = static_cast(len); + if (need < count) + return std::vsnprintf(buf, count, fmt, ap); + + std::vector full(need + 1); + std::vsnprintf(full.data(), full.size(), fmt, ap); + std::memcpy(buf, full.data(), count); + return need == count ? len : -1; +} + +int _snprintf(char* buf, size_t count, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + const int ret = _vsnprintf(buf, count, fmt, ap); + va_end(ap); + return ret; +} + +DWORD timeGetTime() +{ + using namespace std::chrono; + const auto ms = duration_cast(steady_clock::now().time_since_epoch()).count(); + return static_cast(static_cast(ms)); +} + +DWORD GetTickCount() +{ + return timeGetTime(); +} + +#endif // !_WIN32 diff --git a/extension/src/port/common/Win32Crt.h b/extension/src/port/common/Win32Crt.h new file mode 100644 index 00000000..38dd0b6a --- /dev/null +++ b/extension/src/port/common/Win32Crt.h @@ -0,0 +1,41 @@ +#pragma once +// The Win32/MSVC-CRT functions and macros that 40250 logic code calls, with MSVC semantics. +// Added on demand as ported units need them; each one keeps the Windows behavior (including +// the legacy `_snprintf` truncation contract), not the nearest POSIX spelling. + +#include "Win32Types.h" + +#include +#include + +#if !defined(_WIN32) +#include + +#define ZeroMemory(dst, len) std::memset((dst), 0, (len)) +#define FillMemory(dst, len, fill) std::memset((dst), (fill), (len)) +#define CopyMemory(dst, src, len) std::memcpy((dst), (src), (len)) +#define MoveMemory(dst, src, len) std::memmove((dst), (src), (len)) + +inline int _stricmp(const char* a, const char* b) { return strcasecmp(a, b); } +inline int stricmp(const char* a, const char* b) { return strcasecmp(a, b); } +inline int _strnicmp(const char* a, const char* b, size_t n) { return strncasecmp(a, b, n); } +inline int strnicmp(const char* a, const char* b, size_t n) { return strncasecmp(a, b, n); } + +char* _strlwr(char* s); +char* _strupr(char* s); + +// MSVC `_snprintf`/`_vsnprintf`: when the output is longer than `count`, `count` bytes are written +// with no terminator and -1 is returned; exactly `count` bytes are written unterminated and +// `count` is returned; otherwise the output is terminated and its length returned. +int _vsnprintf(char* buf, size_t count, const char* fmt, va_list ap); +int _snprintf(char* buf, size_t count, const char* fmt, ...) +#if defined(__GNUC__) + __attribute__((format(printf, 3, 4))) +#endif + ; + +// winmm `timeGetTime`: milliseconds since system start, wrapping at 2^32. +DWORD timeGetTime(); +DWORD GetTickCount(); + +#endif // !_WIN32 diff --git a/extension/src/port/common/Win32Types.h b/extension/src/port/common/Win32Types.h new file mode 100644 index 00000000..fd1c37a4 --- /dev/null +++ b/extension/src/port/common/Win32Types.h @@ -0,0 +1,136 @@ +#pragma once +// Win32 scalar and handle types with the widths the 40250 client was built with (32-bit Win32, +// ILP32). Scalars are fixed-width on every target; handle/pointer types stay pointer-sized and +// belong behind platform adapters. `long` itself cannot be redefined: ported code that relies on +// the 32-bit `long` uses LONG/DWORD or an explicit int32_t/uint32_t (docs/PORT-PLAN.md section 3). + +#include +#include + +#if defined(_WIN32) +// Windows (LLP64) already has these names with the same widths; use the SDK's so platform code +// that includes in the same translation unit does not conflict. +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else + +typedef uint8_t BYTE; +typedef uint16_t WORD; +typedef uint32_t DWORD; +typedef int32_t LONG; +typedef uint32_t ULONG; +typedef int32_t BOOL; +typedef int32_t INT; +typedef uint32_t UINT; +typedef int16_t SHORT; +typedef uint16_t USHORT; +typedef char CHAR; +typedef uint8_t UCHAR; +typedef float FLOAT; +typedef int64_t LONGLONG; +typedef uint64_t ULONGLONG; +typedef int64_t INT64; +typedef uint64_t UINT64; +typedef uint64_t DWORD64; +typedef wchar_t WCHAR; + +typedef void* LPVOID; +typedef const void* LPCVOID; +typedef void* PVOID; +typedef char* LPSTR; +typedef const char* LPCSTR; +typedef BYTE* LPBYTE; +typedef BYTE* PBYTE; +typedef WORD* LPWORD; +typedef DWORD* LPDWORD; +typedef DWORD* PDWORD; +typedef LONG* LPLONG; +typedef BOOL* LPBOOL; +typedef INT* LPINT; + +typedef intptr_t INT_PTR; +typedef uintptr_t UINT_PTR; +typedef intptr_t LONG_PTR; +typedef uintptr_t ULONG_PTR; +typedef uintptr_t DWORD_PTR; +typedef uintptr_t WPARAM; +typedef intptr_t LPARAM; +typedef intptr_t LRESULT; + +typedef void* HANDLE; +struct HWND__; +typedef HWND__* HWND; +struct HINSTANCE__; +typedef HINSTANCE__* HINSTANCE; +typedef HINSTANCE HMODULE; +struct HDC__; +typedef HDC__* HDC; + +typedef struct tagPOINT +{ + LONG x; + LONG y; +} POINT, *PPOINT, *LPPOINT; + +typedef struct tagRECT +{ + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT, *PRECT, *LPRECT; + +typedef struct tagSIZE +{ + LONG cx; + LONG cy; +} SIZE, *PSIZE, *LPSIZE; + +#ifndef VOID +#define VOID void +#endif +#ifndef CONST +#define CONST const +#endif +#ifndef IN +#define IN +#endif +#ifndef OUT +#define OUT +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#define CALLBACK +#define WINAPI +#define APIENTRY +#define __stdcall +#define __cdecl +#define __forceinline inline __attribute__((always_inline)) + +#define MAX_PATH 260 +#define INFINITE 0xFFFFFFFF + +#define LOBYTE(w) ((BYTE)(((DWORD_PTR)(w)) & 0xff)) +#define HIBYTE(w) ((BYTE)((((DWORD_PTR)(w)) >> 8) & 0xff)) +#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff)) +#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff)) +#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8)) +#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) + +#endif // _WIN32 + +// Widths the 40250 binary was compiled with; serialized structs depend on them. +static_assert(sizeof(BYTE) == 1 && sizeof(WORD) == 2 && sizeof(DWORD) == 4, "Win32 unsigned widths"); +static_assert(sizeof(LONG) == 4 && sizeof(BOOL) == 4 && sizeof(UINT) == 4 && sizeof(INT) == 4, "Win32 signed widths"); +static_assert(sizeof(SHORT) == 2 && sizeof(FLOAT) == 4, "Win32 short/float widths"); +static_assert(sizeof(POINT) == 8 && sizeof(RECT) == 16, "Win32 POINT/RECT layout"); +static_assert(sizeof(WPARAM) == sizeof(void*) && sizeof(HANDLE) == sizeof(void*), "handles stay pointer-sized"); diff --git a/extension/tests/port_common_test.cpp b/extension/tests/port_common_test.cpp new file mode 100644 index 00000000..dfb14d5a --- /dev/null +++ b/extension/tests/port_common_test.cpp @@ -0,0 +1,55 @@ +// port/common: Win32 widths and MSVC CRT semantics the ported 40250 logic relies on. +#include "common/StdAfx.h" + +#include +#include + +static int g_failures = 0; +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \ + ++g_failures; \ + } \ + } while (0) + +int main() +{ + // MSVC _snprintf: shorter output is terminated. + char buf[8]; + std::memset(buf, 'x', sizeof(buf)); + CHECK(_snprintf(buf, 8, "%s", "abc") == 3); + CHECK(std::strcmp(buf, "abc") == 0); + + // Exactly `count` bytes: written unterminated, returns count. + std::memset(buf, 'x', sizeof(buf)); + CHECK(_snprintf(buf, 4, "%s", "abcd") == 4); + CHECK(std::memcmp(buf, "abcdx", 5) == 0); + + // Longer: `count` bytes written unterminated, returns -1. + std::memset(buf, 'x', sizeof(buf)); + CHECK(_snprintf(buf, 4, "%d", 123456) == -1); + CHECK(std::memcmp(buf, "1234x", 5) == 0); + + CHECK(stricmp("PLAYER", "player") == 0); + CHECK(_strnicmp("d:/ymir work/A", "D:/YMIR WORK/b", 12) == 0); + char s[] = "MixEd"; + CHECK(std::strcmp(_strlwr(s), "mixed") == 0); + + CHECK(MAKELONG(0x1234, 0xABCD) == static_cast(0xABCD1234u)); + CHECK(LOWORD(0xABCD1234u) == 0x1234 && HIWORD(0xABCD1234u) == 0xABCD); + CHECK(MAKEWORD(0x34, 0x12) == 0x1234); + + // DWORD arithmetic wraps like the 32-bit original. + DWORD t = 0xFFFFFFF0u; + t += 0x20; + CHECK(t == 0x10); + const DWORD a = timeGetTime(); + const DWORD b = timeGetTime(); + CHECK(static_cast(b - a) < 1000); + + if (g_failures) + return EXIT_FAILURE; + std::puts("port_common_test: ok"); + return EXIT_SUCCESS; +} diff --git a/script/port_gate.sh b/script/port_gate.sh new file mode 100755 index 00000000..7d04decd --- /dev/null +++ b/script/port_gate.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Batch 2A gate (docs/PORT-PLAN.md): build port_logic + the header self-containment gate per +# platform. A platform whose toolchain is missing prints BLOCKED and never counts as a pass. +# +# script/port_gate.sh [macos|android|ios|windows|linux ...] (default: all) +# +# Build trees go to $MT_PORT_GATE_DIR (default build-port-gate/). Toolchains: +# android ANDROID_NDK_HOME / ANDROID_NDK_ROOT (NDK 27.2) +# ios Xcode with the iphoneos SDK +# windows MT_WIN_CXX (default x86_64-w64-mingw32-g++); MSVC is covered on a Windows host +# linux MT_LINUX_TOOLCHAIN (a CMake toolchain file), or a Linux host +set -uo pipefail +cd "$(dirname "$0")/.." + +OUT="${MT_PORT_GATE_DIR:-build-port-gate}" +PLATFORMS=("$@") +[ ${#PLATFORMS[@]} -eq 0 ] && PLATFORMS=(macos android ios windows linux) +PORT_ONLY=(-DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_PORT=ON) +declare -a RESULTS=() + +run() { # name, build dir, configure args... + local name="$1" dir="$2"; shift 2 + if cmake -S . -B "$dir" "$@" >"$dir.log" 2>&1 \ + && cmake --build "$dir" --target port_logic ${BUILD_EXTRA[@]+"${BUILD_EXTRA[@]}"} >>"$dir.log" 2>&1; then + RESULTS+=("$name PASS") + else + RESULTS+=("$name FAIL (see $dir.log)") + fi +} + +mkdir -p "$OUT" +for p in "${PLATFORMS[@]}"; do + BUILD_EXTRA=() + case "$p" in + macos) + BUILD_EXTRA=(--target port_common_test) + run macos "$OUT/macos" "${PORT_ONLY[@]}" -DBUILD_TESTING=ON + last=$((${#RESULTS[@]} - 1)) + if [[ "${RESULTS[$last]}" == *PASS ]] && ! ctest --test-dir "$OUT/macos" -R '^port\.' --output-on-failure >>"$OUT/macos.log" 2>&1; then + RESULTS[$last]="macos FAIL (port tests, see $OUT/macos.log)" + fi + ;; + android) + NDK="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}" + SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}}" + if [ -z "$NDK" ] && [ -d "$SDK/ndk" ]; then + NDK="$SDK/ndk/$(ls -1 "$SDK/ndk" | sort -V | tail -1)" + fi + if [ -z "$NDK" ] || [ ! -f "$NDK/build/cmake/android.toolchain.cmake" ]; then + RESULTS+=("android BLOCKED (no NDK)"); continue + fi + run android "$OUT/android" "${PORT_ONLY[@]}" -DBUILD_TESTING=OFF \ + -DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \ + -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 + ;; + ios) + if ! xcrun --sdk iphoneos --show-sdk-path >/dev/null 2>&1; then + RESULTS+=("ios BLOCKED (no iphoneos SDK)"); continue + fi + BUILD_EXTRA=(-- -sdk iphoneos CODE_SIGNING_ALLOWED=NO) + run ios "$OUT/ios" "${PORT_ONLY[@]}" -DBUILD_TESTING=OFF -G Xcode -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 + ;; + windows) + CXX_W="${MT_WIN_CXX:-x86_64-w64-mingw32-g++}" + if [[ "$(uname -s)" != MINGW* ]] && ! command -v "$CXX_W" >/dev/null; then + RESULTS+=("windows BLOCKED (no $CXX_W)"); continue + fi + run windows "$OUT/windows" "${PORT_ONLY[@]}" -DBUILD_TESTING=OFF -DCMAKE_SYSTEM_NAME=Windows \ + -DCMAKE_C_COMPILER="${CXX_W%g++}gcc" -DCMAKE_CXX_COMPILER="$CXX_W" + ;; + linux) + if [ "$(uname -s)" = Linux ]; then + BUILD_EXTRA=(--target port_common_test) + run linux "$OUT/linux" "${PORT_ONLY[@]}" -DBUILD_TESTING=ON + elif [ -n "${MT_LINUX_TOOLCHAIN:-}" ]; then + run linux "$OUT/linux" "${PORT_ONLY[@]}" -DBUILD_TESTING=OFF -DCMAKE_TOOLCHAIN_FILE="$MT_LINUX_TOOLCHAIN" + else + RESULTS+=("linux BLOCKED (not a Linux host and no MT_LINUX_TOOLCHAIN)") + fi + ;; + *) RESULTS+=("$p UNKNOWN") ;; + esac +done + +printf '%s\n' "${RESULTS[@]}" +! printf '%s\n' "${RESULTS[@]}" | grep -q FAIL