port(2A): port/common Win32 layer, port_logic target and header gate
- Win32Types.h: fixed-width Win32 scalars, pointer-sized handles, static_asserts
- Win32Crt.{h,cpp}: MSVC _snprintf truncation contract, stricmp, timeGetTime wrap
- port_logic static lib linked into libmtgodot; standalone MTGODOT_BUILD_PORT build
- port_header_gate: every port/**.h compiles alone (twice) with its lib's
40250 Distribute|Win32 defines (NDEBUG, USE_LOD, _DISTRIBUTE)
- script/port_gate.sh: macOS/Android/iOS/Windows(mingw) PASS, Linux BLOCKED
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
/build-ios/
|
||||
/build-android/
|
||||
/build-classic/
|
||||
/build-port-gate/
|
||||
/cmake-build-*/
|
||||
compile_commands.json
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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`(多包覆盖顺序、路径归一化)、
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# port_logic — mirror of the 40250 client logic (extension/src/port/<Lib>/<File>.{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/<Lib>/<Lib>.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 $<TARGET_FILE:port_common_test>)
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
// Base of every ported library's slim StdAfx.h. 40250's EterBase/StdAfx.h pulls in <windows.h>
|
||||
// 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 <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "Win32Crt.h"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
char* _strlwr(char* s)
|
||||
{
|
||||
for (char* p = s; *p; ++p)
|
||||
*p = static_cast<char>(std::tolower(static_cast<unsigned char>(*p)));
|
||||
return s;
|
||||
}
|
||||
|
||||
char* _strupr(char* s)
|
||||
{
|
||||
for (char* p = s; *p; ++p)
|
||||
*p = static_cast<char>(std::toupper(static_cast<unsigned char>(*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<size_t>(len);
|
||||
if (need < count)
|
||||
return std::vsnprintf(buf, count, fmt, ap);
|
||||
|
||||
std::vector<char> 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<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
||||
return static_cast<DWORD>(static_cast<uint64_t>(ms));
|
||||
}
|
||||
|
||||
DWORD GetTickCount()
|
||||
{
|
||||
return timeGetTime();
|
||||
}
|
||||
|
||||
#endif // !_WIN32
|
||||
@@ -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 <cstdarg>
|
||||
#include <cstring>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <strings.h>
|
||||
|
||||
#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
|
||||
@@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Windows (LLP64) already has these names with the same widths; use the SDK's so platform code
|
||||
// that includes <windows.h> 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 <windows.h>
|
||||
#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");
|
||||
@@ -0,0 +1,55 @@
|
||||
// port/common: Win32 widths and MSVC CRT semantics the ported 40250 logic relies on.
|
||||
#include "common/StdAfx.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
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<LONG>(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<DWORD>(b - a) < 1000);
|
||||
|
||||
if (g_failures)
|
||||
return EXIT_FAILURE;
|
||||
std::puts("port_common_test: ok");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Executable
+87
@@ -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
|
||||
Reference in New Issue
Block a user