diff --git a/.gitignore b/.gitignore index d0bec29cb..2b1df646a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ src/source/Global Release/ src/Global Release/ src/Global Release/Main +# External dependencies (built locally) +_deps/ + # CMake build directories build/ build-mingw/ diff --git a/README.md b/README.md index b7d2aa794..b4ad985e3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ What needs to be done for Season 6: ## How to build & run ### Requirements -* **CMake** 3.16 or newer (bundled with Visual Studio and CLion) +* **CMake** 3.25 or newer (bundled with Visual Studio and CLion) * **.NET SDK 10.0** or newer (for building the Client Library) * **Visual Studio 2022+** with C++ and C# workloads, **CLion**, or **Rider** (see IDE-specific instructions below) * A compatible server: [OpenMU](https://github.com/MUnique/OpenMU) @@ -162,7 +162,8 @@ The project uses **CMake** as its build system. The `.NET Client Library` is aut 3. **Configure working directory:** - Run → Edit Configurations - Select `Main` - - Set "Working directory" to: `$ProjectFileDir$/src/bin` + - Set "Working directory" to the build output directory (e.g. `cmake-build-debug/src/Debug`) + - The post-build step copies all game assets there automatically 4. **Build and Run:** - Click the hammer icon to build diff --git a/docs/build-guide.md b/docs/build-guide.md new file mode 100644 index 000000000..ddf900811 --- /dev/null +++ b/docs/build-guide.md @@ -0,0 +1,315 @@ +# CMake Build System — Platform Guide + +This document explains how the build system works across all supported environments, what directories are created and why, and how to set up each environment correctly. + +--- + +## Quick Reference + +| Environment | Best for | Build speed | Setup effort | +|-------------|----------|-------------|--------------| +| WSL terminal + MinGW | Daily development, Claude Code | Fast | Medium (one-time) | +| CLion via Z: drive + MSVC | Quick MSVC testing (no file copy) | Slow (cross-filesystem I/O) | Easy | +| CLion with native Windows path + MSVC | Debugging, full-speed MSVC builds | Fast | Easy (manual file sync) | +| Windows native + MSVC (VS presets) | Full-speed MSVC builds | Fast | Easy (manual file sync) | + +**Recommended workflow:** Use WSL + MinGW for daily development and Claude Code. Use CLion/MSVC only when you need the debugger or MSVC-specific testing. + +--- + +## Supported Build Scenarios + +| Scenario | Compiler | CMake runs on | dotnet used | Status | +|----------|----------|---------------|-------------|--------| +| Windows (VS presets) | MSVC x86 | Windows | `dotnet.exe` (Windows) | Supported | +| CLion via Z: drive | MSVC x86 | Windows | `dotnet.exe` (Windows) | Supported (slow I/O) | +| CLion with native Windows path | MSVC x86 | Windows | `dotnet.exe` (Windows) | Supported (fast) | +| WSL terminal (MinGW) | MinGW i686 | Linux (WSL) | `dotnet.exe` (Windows, via WSL interop) | Supported | +| Pure Linux (no WSL) | MinGW i686 | Linux | `dotnet` (Linux) | Partial — no DLL | +| CLion on Linux | MinGW i686 | Linux | `dotnet` (Linux) | Untested — no DLL | + +--- + +## Setup Guide + +### WSL Terminal — MinGW (Recommended for Development) + +**Where to keep the repo:** On the WSL filesystem (`/home//MuMain`). This is the fast path — all file I/O stays on native Linux ext4. + +**Do NOT** put the repo on `/mnt/c/...` (Windows filesystem from WSL). That path goes through a slow translation layer and is significantly slower than native WSL filesystem. + +**Prerequisites (one-time):** + +```bash +sudo apt-get update && sudo apt-get install -y mingw-w64 g++-mingw-w64-i686 cmake ninja-build +``` + +**Configure and build:** + +```bash +cmake -S . -B build-mingw -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-i686.cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_EDITOR=ON \ + -DMU_TURBOJPEG_STATIC_LIB=_deps/mingw-i686/lib/libturbojpeg.a + +cmake --build build-mingw -j$(nproc) +``` + +**Running the game:** The post-build step copies all assets from `src/bin/` into `build-mingw/src/`. Run from the build output directory: + +```bash +cd build-mingw/src && ./Main.exe +``` + +### CLion on Windows — Which Approach? + +CLion on Windows uses the MSVC compiler. There are two ways to point CLion at the source code: + +| Approach | How it works | Tradeoff | +|----------|-------------|----------| +| **Z: drive** (mapped WSL filesystem) | CLion reads source files directly from your WSL repo via a mapped network drive. No file copying needed. | Slower builds — every file read crosses the WSL↔Windows boundary. | +| **Native Windows path** | You copy/clone the repo onto a Windows drive (e.g. `D:\repos\MuMain`). CLion reads files from fast local NTFS. | Faster builds, but you maintain a separate copy of the repo. | + +Pick **Z: drive** if you want a single repo checkout and don't mind slower builds. +Pick **native Windows path** if you want fast MSVC builds and are okay syncing/copying files manually. + +Both approaches produce identical output. The only difference is build speed. + +--- + +### CLion on Windows via Z: Drive + +This approach maps your WSL filesystem as a Windows drive letter so CLion can open the repo directly — no file copying needed. + +**Prerequisites:** + +1. The repo must already exist on the WSL filesystem (e.g. `/home//MuMain`). +2. WSL must be running. +3. CLion must be installed on Windows. + +**Step 1 — Map the WSL filesystem as Z: drive (one-time, persistent across reboots):** + +Open PowerShell and run: + +```powershell +net use Z: \\wsl.localhost\Ubuntu /persistent:yes +``` + +This makes your entire WSL home directory accessible as `Z:\home\\` from Windows. The `/persistent:yes` flag means the mapping survives reboots (as long as WSL is running). + +**Step 2 — Fix git "dubious ownership" warning (one-time):** + +Windows git flags WSL-owned repos as untrusted. Without this fix, CLion's git integration won't work. Run in PowerShell: + +```powershell +git config --global --add safe.directory '%(prefix)///wsl.localhost/Ubuntu/home//MuMain' +git config --global --add safe.directory '%(prefix)///wsl.localhost/Ubuntu/home//MuMain/src/ThirdParty/imgui' +``` + +Note: You must use the UNC format above (`///wsl.localhost/...`), not `Z:/...` — git internally resolves the Z: drive back to the UNC path. + +**Step 3 — Open the project in CLion:** + +Open `Z:\home\\MuMain` in CLion (File > Open). + +**Step 4 — Configure CMake in CLion** (Settings > Build, Execution, Deployment > CMake): + +- **CMake options:** + ``` + -G Ninja -DCMAKE_TOOLCHAIN_FILE=Z:/home//MuMain/toolchain-x86.cmake -DENABLE_EDITOR=ON + ``` +- **Build directory:** Set to a **Windows-native path** for faster builds: + ``` + C:\build\MuMain-x86debug-mueditor + ``` + This is important: even though source files are on Z:, putting build output on a local Windows drive (C: or D:) avoids slow cross-filesystem writes. + +**Step 5 — Set up the Run Configuration:** + +Set the **Working directory** to the build output directory: +``` +C:\build\MuMain-x86debug-mueditor\src\Debug +``` + +Do NOT set it to `Z:\...\src\bin`. The post-build step automatically copies all game assets (data files, textures, etc.) into the build output directory, and using the Windows-native path avoids slow cross-filesystem reads at runtime. + +--- + +### CLion on Windows with Native Windows Path + +This approach uses a repo cloned directly on a Windows drive. Everything is on fast local NTFS — no cross-filesystem penalty. + +**Prerequisites:** + +1. Clone or copy the repo onto a Windows drive (e.g. `D:\repos\MuMain`). +2. CLion must be installed on Windows. + +**Step 1 — Open the project in CLion:** + +Open `D:\repos\MuMain` in CLion (File > Open). + +**Step 2 — Configure CMake in CLion** (Settings > Build, Execution, Deployment > CMake): + +- **CMake options:** + ``` + -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:/repos/MuMain/toolchain-x86.cmake -DENABLE_EDITOR=ON + ``` +- **Build directory:** Can use CLion's default (inside the project) or a custom path. Both are fast since everything is on native NTFS. + +**Step 3 — Set up the Run Configuration:** + +Set the **Working directory** to the build output directory: +``` +D:\repos\MuMain\cmake-build-x86debug-mueditor\src\Debug +``` + +The post-build step copies all game assets there automatically. + +**Note:** Visual Studio cannot open projects from the Z: drive (WSL filesystem), but works fine with native Windows paths like `D:\repos\MuMain`. + +### Windows — Visual Studio Presets + +```powershell +cmake --preset windows-x86 # standard x86 +cmake --preset windows-x86-mueditor # x86 with ImGui editor + +cmake --build --preset windows-x86-debug +cmake --build --preset windows-x86-mueditor-debug +``` + +**Working directory for running:** `out/build/windows-x86/src/Debug/` (assets are copied there by the post-build step). + +--- + +## Performance Notes + +| Operation | WSL filesystem | Z: drive from Windows | Native Windows (C:\) | +|-----------|---------------|----------------------|---------------------| +| File reads (compilation) | Fast | Slow | Fast | +| File writes (build output) | Fast | Slow | Fast | +| Git operations | Fast | Slow | Fast | +| Claude Code file operations | Fast | N/A | N/A | +| Game runtime (data file reads) | Fast (WSL interop) | Slow | Fast | + +**Key takeaway:** Keep the repo on WSL for Claude Code. For CLion/MSVC, put the build output on a Windows drive. Accept that source file reads from Z: will be slower — this is a tradeoff for having a single repo checkout. + +--- + +## Directory Layout Per Scenario + +### 1. Windows — Visual Studio Presets + +``` +MuMain/ <-- on Windows filesystem (C:\...) + .nuget/ <-- NuGet package cache (default, configurable) + out/build/windows-x86/ <-- build output (CMAKE_BINARY_DIR) + src/ + Debug/ + Main.exe <-- game executable + copied assets + MUnique.Client.Library.dll + dotnet_out/ <-- intermediate .NET publish output + .dotnet_temp/ <-- .NET CLI temp (avoids umlaut paths) +``` + +### 2. CLion on Windows via Z: Drive + +``` +Z:\home\\MuMain\ <-- source (mapped WSL filesystem) + .nuget\ <-- NuGet cache (in project root) + +C:\build\MuMain-x86debug-mueditor\ <-- build directory (Windows-native, fast) + src/ + Debug/ + Main.exe <-- game executable + copied assets + MUnique.Client.Library.dll + dotnet_out/ + .dotnet_temp/ +``` + +**Note:** If `ilc.exe` from the NuGet cache fails to execute on the WSL filesystem, override with a Windows-native path: `cmake -DMU_NUGET_CACHE_DIR=C:/.mu-nuget ...` + +### 3. CLion on Windows with Native Windows Path + +``` +D:\repos\MuMain\ <-- source (on Windows drive) + .nuget\ <-- NuGet cache (in project root) + cmake-build-x86debug-mueditor\ <-- build directory (CLion default or custom) + src/ + Debug/ + Main.exe <-- game executable + copied assets + MUnique.Client.Library.dll + dotnet_out/ + .dotnet_temp/ +``` + +### 4. WSL Terminal — MinGW Cross-Compile + +``` +/home//MuMain/ <-- source + build (all on WSL filesystem) + .nuget/ <-- NuGet cache (in project root) + build-mingw/ <-- build output (CMAKE_BINARY_DIR) + src/ + Main.exe <-- PE32 Windows executable + copied assets + MUnique.Client.Library.dll + dotnet_out/ + .dotnet_temp/ +``` + +### 5. Pure Linux (No WSL, No Windows dotnet) + +``` +/home//MuMain/ + build-mingw/ + src/ + Main.exe <-- built, but no DLL +``` + +The Linux `dotnet` SDK cannot produce a Windows `.dll` via Native AOT (cross-OS AOT is not supported). CMake prints a warning and skips the .NET build. The game runs but cannot connect to a server. + +--- + +## NuGet Cache Location + +The NuGet cache defaults to `/.nuget` on all platforms. This keeps it inside the repo (already in `.gitignore`) and avoids issues with special characters in user profile paths. + +Override with: + +```bash +cmake -DMU_NUGET_CACHE_DIR=D:/my-nuget-cache ... +``` + +**Why not the default NuGet cache?** The system default is `%USERPROFILE%\.nuget\packages`. If the Windows username contains special characters (e.g. umlauts), the Native AOT IL compiler (`ilc.exe`) can fail. Using a project-local path avoids this. + +**Edge case — CLion via Z: drive:** The NuGet cache contains native executables like `ilc.exe`. Windows may fail to execute `.exe` files stored on the WSL ext4 filesystem via `\\wsl.localhost\`. If this happens, override to a Windows-native path: `cmake -DMU_NUGET_CACHE_DIR=C:/.mu-nuget ...` + +--- + +## How dotnet Is Found + +```cmake +find_program(DOTNET_EXECUTABLE dotnet.exe) # prefer Windows binary +if (NOT DOTNET_EXECUTABLE) + find_program(DOTNET_EXECUTABLE dotnet) # fallback to Linux binary +endif() +``` + +- **WSL:** Finds `dotnet.exe` at `/mnt/c/Program Files/dotnet/dotnet.exe` via WSL interop. Produces correct Windows DLLs with Native AOT. +- **Native Windows:** Finds `dotnet.exe` directly. +- **Pure Linux:** Falls back to `dotnet`. Cannot produce Windows DLLs. +- **No SDK installed:** CMake warns and skips the .NET build. + +--- + +## Path Translation (WSL Only) + +When CMake detects it's running on Linux (`CMAKE_HOST_SYSTEM_NAME == "Linux"`) and found a Windows `dotnet.exe`, it converts all paths passed to `dotnet.exe` using `wslpath -w`: + +| Linux path | Converted Windows path | +|------------|----------------------| +| `/home//MuMain/ClientLibrary/...csproj` | `\\wsl.localhost\Ubuntu\home\\MuMain\ClientLibrary\...csproj` | +| `/home//MuMain/build-mingw/dotnet_out` | `\\wsl.localhost\Ubuntu\home\\MuMain\build-mingw\dotnet_out` | + +Paths used by CMake-native commands (`copy_if_different`, `DEPENDS`) stay as Linux paths. + +The guard `CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux"` ensures `wslpath` is never called on native Windows (where it doesn't exist). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8924fd46..2c1154134 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -267,19 +267,71 @@ if (MSVC) $ COMMENT "Copying required DLLs to build directory" ) +endif() + +# .NET Client Library and tools (platform-agnostic, requires dotnet SDK) +# Native AOT can only target the OS it runs on, so when cross-compiling from +# WSL we need the Windows dotnet.exe (available via WSL interop) rather than +# a Linux dotnet. +find_program(DOTNET_EXECUTABLE dotnet.exe) +if (NOT DOTNET_EXECUTABLE) + find_program(DOTNET_EXECUTABLE dotnet) +endif() +if (DOTNET_EXECUTABLE) + # Native AOT cannot cross-compile across OS boundaries. A Linux dotnet + # targeting win-x86/win-x64 will fail. Only proceed when we have a Windows + # dotnet.exe (native or via WSL interop). + if (NOT DOTNET_EXECUTABLE MATCHES "\\.exe$" AND CMAKE_SYSTEM_NAME STREQUAL "Windows") + message(WARNING "Found Linux dotnet but target is Windows. " + "Cross-OS Native AOT is not supported. MUnique.Client.Library.dll will NOT be built. " + "Install the Windows .NET SDK or use WSL interop (dotnet.exe) to enable.") + set(DOTNET_EXECUTABLE "") + endif() +endif() +if (DOTNET_EXECUTABLE) + message(STATUS "Found .NET SDK: ${DOTNET_EXECUTABLE}") + + # Helper macro: convert a path to Windows-native format when using + # Windows dotnet.exe from WSL. On all other platforms this is a no-op. + function(mu_native_path input_path output_var) + if (DOTNET_EXECUTABLE MATCHES "\\.exe$" AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + execute_process( + COMMAND wslpath -w "${input_path}" + OUTPUT_VARIABLE native_path + RESULT_VARIABLE rc + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (rc EQUAL 0) + set(${output_var} "${native_path}" PARENT_SCOPE) + else() + message(FATAL_ERROR "wslpath failed for '${input_path}'. Ensure wslpath is available.") + endif() + else() + set(${output_var} "${input_path}" PARENT_SCOPE) + endif() + endfunction() # 1. Define the output path using a variable CMake can understand early set(DOTNET_DLL_PATH "${CMAKE_CURRENT_BINARY_DIR}/MUnique.Client.Library.dll") set(DOTNET_PROJ "${CMAKE_CURRENT_SOURCE_DIR}/../ClientLibrary/MUnique.Client.Library.csproj") - # Create temp directories for Native AOT build (avoiding paths with umlauts) + # Create temp directories for Native AOT build. set(DOTNET_TEMP_OUTPUT "${CMAKE_BINARY_DIR}/dotnet_out") set(DOTNET_TEMP_DIR "${CMAKE_BINARY_DIR}/.dotnet_temp") - set(DOTNET_NUGET_DIR "${CMAKE_SOURCE_DIR}/.nuget") + + # NuGet cache: defaults to /.nuget, override with -DMU_NUGET_CACHE_DIR=... + set(MU_NUGET_CACHE_DIR "${CMAKE_SOURCE_DIR}/.nuget" CACHE PATH "NuGet package cache directory") + set(DOTNET_NUGET_DIR "${MU_NUGET_CACHE_DIR}") file(MAKE_DIRECTORY "${DOTNET_TEMP_OUTPUT}") file(MAKE_DIRECTORY "${DOTNET_TEMP_DIR}") file(MAKE_DIRECTORY "${DOTNET_NUGET_DIR}") + # Convert paths to Windows-native format for MSBuild (no-op outside WSL). + mu_native_path("${DOTNET_PROJ}" DOTNET_PROJ_NATIVE) + mu_native_path("${DOTNET_TEMP_OUTPUT}" DOTNET_TEMP_OUTPUT_NATIVE) + mu_native_path("${DOTNET_NUGET_DIR}" DOTNET_NUGET_DIR_NATIVE) + mu_native_path("${DOTNET_TEMP_DIR}" DOTNET_TEMP_DIR_NATIVE) + # 2. Tell CMake to watch ALL .cs files in the ClientLibrary folder file(GLOB_RECURSE DOTNET_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ClientLibrary/*.cs") @@ -300,11 +352,11 @@ if (MSVC) OUTPUT "${DOTNET_DLL_PATH}" COMMAND ${CMAKE_COMMAND} -E echo "--- C# Changes Detected: Rebuilding Client Library ---" COMMAND ${CMAKE_COMMAND} -E env - "NUGET_PACKAGES=${DOTNET_NUGET_DIR}" - "DOTNET_CLI_HOME=${DOTNET_TEMP_DIR}" - "TEMP=${DOTNET_TEMP_DIR}" - "TMP=${DOTNET_TEMP_DIR}" - dotnet publish "${DOTNET_PROJ}" -c $ -r ${DOTNET_RID} -p:PlatformTarget=${DOTNET_PLATFORM} -o "${DOTNET_TEMP_OUTPUT}" --nologo + "NUGET_PACKAGES=${DOTNET_NUGET_DIR_NATIVE}" + "DOTNET_CLI_HOME=${DOTNET_TEMP_DIR_NATIVE}" + "TEMP=${DOTNET_TEMP_DIR_NATIVE}" + "TMP=${DOTNET_TEMP_DIR_NATIVE}" + "${DOTNET_EXECUTABLE}" publish "${DOTNET_PROJ_NATIVE}" -c $ -r ${DOTNET_RID} -p:PlatformTarget=${DOTNET_PLATFORM} -o "${DOTNET_TEMP_OUTPUT_NATIVE}" --nologo COMMAND ${CMAKE_COMMAND} -E copy_if_different "${DOTNET_TEMP_OUTPUT}/MUnique.Client.Library.dll" "${DOTNET_DLL_PATH}" DEPENDS "${DOTNET_PROJ}" ${DOTNET_SOURCES} COMMENT "Checking for .NET Client Library updates..." @@ -329,18 +381,23 @@ if (MSVC) # ConstantsReplacer build target set(CONSTANTS_REPLACER_PROJ "${CMAKE_CURRENT_SOURCE_DIR}/../ConstantsReplacer/ConstantsReplacer.csproj") + mu_native_path("${CONSTANTS_REPLACER_PROJ}" CONSTANTS_REPLACER_PROJ_NATIVE) + mu_native_path("${CMAKE_BINARY_DIR}/ConstantsReplacer" CONSTANTS_REPLACER_OUTDIR_NATIVE) file(GLOB_RECURSE CONSTANTS_REPLACER_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ConstantsReplacer/*.cs") set(CONSTANTS_REPLACER_OUTPUT "${CMAKE_BINARY_DIR}/ConstantsReplacer/ConstantsReplacer.exe") add_custom_command( OUTPUT "${CONSTANTS_REPLACER_OUTPUT}" - COMMAND dotnet build "${CONSTANTS_REPLACER_PROJ}" -c $ -o "${CMAKE_BINARY_DIR}/ConstantsReplacer" --nologo + COMMAND "${DOTNET_EXECUTABLE}" build "${CONSTANTS_REPLACER_PROJ_NATIVE}" -c $ -o "${CONSTANTS_REPLACER_OUTDIR_NATIVE}" --nologo DEPENDS "${CONSTANTS_REPLACER_PROJ}" ${CONSTANTS_REPLACER_SOURCES} COMMENT "Building ConstantsReplacer tool..." VERBATIM ) add_custom_target(ConstantsReplacer DEPENDS "${CONSTANTS_REPLACER_OUTPUT}") +else() + message(WARNING ".NET SDK not found. MUnique.Client.Library.dll will NOT be built. " + "The game will run but cannot connect to the server. Install .NET SDK 10.0+ to enable.") endif() # MinGW (32-bit or 64-bit) cross-toolchains: link against the Windows/OpenGL @@ -415,4 +472,10 @@ endif() # Link ImGui for all platforms when editor is enabled if(ENABLE_EDITOR) target_link_libraries(Main PRIVATE imgui) + # MinGW's linker is order-sensitive: opengl32 and dwmapi (needed by ImGui + # backends) must appear after the imgui static library. MSVC already links + # these earlier and doesn't care about order. + if (NOT MSVC) + target_link_libraries(Main PRIVATE opengl32 dwmapi) + endif() endif()