diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61ebcd3..6f5712f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: run: shell: ${{ matrix.shell }} env: - CMAKE_TEST: cmake-build-host-tests + CMAKE_BUILD: build steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -135,15 +135,15 @@ jobs: - name: Build run: | chmod +x build.sh - ./build.sh + ./build.sh --build-dir ${CMAKE_BUILD} # recursively list all files in build directory - ls -R build + ls -R ${CMAKE_BUILD} # move artifacts mkdir -p artifacts - tar -czf ./artifacts/Moonlight.tar.gz ./build/xbe - mv ./Moonlight.iso ./artifacts + tar -czf ./artifacts/Moonlight.tar.gz ./${CMAKE_BUILD}/xbox/xbe + cp ./${CMAKE_BUILD}/xbox/Moonlight.iso ./artifacts - name: Upload Artifacts uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 @@ -151,16 +151,10 @@ jobs: name: moonlight-${{ runner.os }} path: artifacts/ - - name: Configure host tests - run: cmake -S . -B ${CMAKE_TEST} -DBUILD_TESTS=ON -DBUILD_DOCS=OFF - - - name: Build host tests - run: cmake --build ${CMAKE_TEST} --target test_moonlight - - name: Run tests id: test - working-directory: ${{ env.CMAKE_TEST }} - run: ./tests/test_moonlight --gtest_color=yes --gtest_output=xml:tests/test_results.xml + working-directory: ${{ env.CMAKE_BUILD }}/tests + run: ./test_moonlight --gtest_color=yes --gtest_output=xml:test_results.xml - name: Generate gcov report id: test_report @@ -170,20 +164,20 @@ jobs: (steps.test.outcome == 'success' || steps.test.outcome == 'failure') run: | ${{ steps.python-path.outputs.python-path }} -m pip install ".[test]" - ${{ steps.python-path.outputs.python-path }} -m gcovr ${CMAKE_TEST} -r . \ + ${{ steps.python-path.outputs.python-path }} -m gcovr ${CMAKE_BUILD} -r . \ --filter src \ --exclude-noncode-lines \ --exclude-throw-branches \ --exclude-unreachable-branches \ --verbose \ --xml-pretty \ - -o "${CMAKE_TEST}/coverage.xml" + -o "${CMAKE_BUILD}/coverage.xml" - name: Debug coverage file if: >- always() && steps.test_report.outcome == 'success' - run: cat "${CMAKE_TEST}/coverage.xml" + run: cat "${CMAKE_BUILD}/coverage.xml" - name: Upload coverage # any except canceled or skipped @@ -195,7 +189,7 @@ jobs: with: disable_search: true fail_ci_if_error: true - files: ./${{ env.CMAKE_TEST }}/coverage.xml + files: ./${{ env.CMAKE_BUILD }}/coverage.xml flags: ${{ runner.os }} report_type: coverage token: ${{ secrets.CODECOV_TOKEN }} @@ -211,7 +205,7 @@ jobs: with: disable_search: true fail_ci_if_error: true - files: ./${{ env.CMAKE_TEST }}/tests/test_results.xml + files: ./${{ env.CMAKE_BUILD }}/tests/test_results.xml flags: ${{ runner.os }} handle_no_reports_found: true report_type: test_results diff --git a/.gitignore b/.gitignore index 49485fe..a7fe280 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,5 @@ cmake-*/ .local/ docs/doxyconfig* -# iso file is created in project root -*.iso +# Temporary files +*.cmd~ diff --git a/.run/Run Moonlight ISO in xemu.run.xml b/.run/Run Moonlight ISO in xemu.run.xml deleted file mode 100644 index 6045dbf..0000000 --- a/.run/Run Moonlight ISO in xemu.run.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/.run/moonlight_xbox.run.xml b/.run/moonlight_xbox.run.xml new file mode 100644 index 0000000..fffada2 --- /dev/null +++ b/.run/moonlight_xbox.run.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index fb1f4d7..6238a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,139 +1,69 @@ +# Configure the host-native top-level build, bootstrap vendored nxdk prerequisites, +# and delegate the Xbox target to an internal child configure that uses the stock nxdk toolchain. + cmake_minimum_required(VERSION 3.18) -# much of this file is borrowed from https://github.com/Ryzee119/Xenium-Tools/blob/master/CMakeLists.txt -# and https://github.com/abaire/nxdk_pgraph_tests/blob/main/CMakeLists.txt # Allow third-party subdirectories that use cmake_minimum_required < 3.5 (removed in CMake 4.x) set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "") -project(Moonlight C CXX) +set(MOONLIGHT_BUILD_KIND "HOST" CACHE STRING "Internal Moonlight build mode") +set_property(CACHE MOONLIGHT_BUILD_KIND PROPERTY STRINGS HOST XBOX) -# -# metadata -# -set(XBE_TITLE ${CMAKE_PROJECT_NAME}) -set(NXDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third-party/nxdk") -set(XBOX_XBE_DIR "${CMAKE_CURRENT_BINARY_DIR}/xbe") -set(XBOX_ISO "${CMAKE_PROJECT_NAME}.iso") - -# -# Options -# -option(BUILD_DOCS "Build documentation" ON) -option(BUILD_TESTS "Build tests" OFF) +if(MOONLIGHT_BUILD_KIND STREQUAL "XBOX") + project(Moonlight C CXX) -# add custom modules -set(CMAKE_MODULE_PATH - ${CMAKE_MODULE_PATH} - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/" -) + set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/" + ) -set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(NXDK REQUIRED) -find_package(NXDK_SDL2 REQUIRED) -find_package(NXDK_SDL2_Image REQUIRED) + option(BUILD_DOCS "Build documentation" OFF) + option(MOONLIGHT_SKIP_NXDK_PREP "Skip nxdk bootstrap during the internal Xbox child configure" OFF) -# add the automount_d_drive symbol to the linker flags, this is automatic with nxdk when using the Makefile option -# if this is not used, we must add some code to the main function to automount the D drive -# e.g. https://github.com/abaire/nxdk_pgraph_tests/blob/4b7934e6d612a6d17f9ec229a2d72601a5caefc4/src/main.cpp#L118-L122 -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -include:_automount_d_drive") + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/xbox-build.cmake") + return() +endif() -# create the xbe directory if it doesn't exist -file(MAKE_DIRECTORY ${XBOX_XBE_DIR}) +project(Moonlight C CXX) -add_custom_target(sync_xbe_assets ALL - COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/xbe" - "${XBOX_XBE_DIR}" - COMMENT "Sync XBE assets" +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/" ) -include(FindPkgConfig) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) +if(CMAKE_HOST_WIN32 AND CMAKE_GENERATOR MATCHES "Makefiles") + set(CMAKE_DEPENDS_USE_COMPILER FALSE CACHE BOOL "Use CMake depfile scanning with Windows makefile generators" FORCE) endif() -set(CMAKE_CXX_FLAGS_RELEASE "-O2") -set(CMAKE_C_FLAGS_RELEASE "-O2") -file(GLOB_RECURSE MOONLIGHT_SOURCES - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" -) -set(MOONLIGHT_EXTERNAL_LIBRARIES - NXDK::NXDK - NXDK::NXDK_CXX - NXDK::SDL2 - NXDK::SDL2_Image -) -set(MOONLIGHT_INCLUDE_DIRS -) -set(MOONLIGHT_COMPILE_OPTIONS "-Wno-builtin-macro-redefined") - -# submodules -# moonlight common library -# find_package(OpenSSL REQUIRED) -include(GetOpenSSL REQUIRED) -set(ENET_NO_INSTALL ON CACHE BOOL "Don't install any libraries built for enet") -set(BUILD_SHARED_LIBS OFF) -add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c") -if(TARGET moonlight-common-c AND TARGET openssl_external) - add_dependencies(moonlight-common-c openssl_external) -endif() -target_link_libraries(enet PUBLIC NXDK::NXDK NXDK::Net NXDK::ws2_32) -if(TARGET moonlight-common-c) - target_link_libraries(moonlight-common-c PRIVATE NXDK::ws2_32) -endif() +option(BUILD_DOCS "Build documentation" ON) +option(BUILD_TESTS "Build host-native unit tests" ON) +option(BUILD_XBOX "Build the Xbox target through an internal child configure" ON) +option(MOONLIGHT_FORCE_NXDK_DISTCLEAN "Force a fresh nxdk distclean during configure" OFF) -add_executable(${CMAKE_PROJECT_NAME} - ${MOONLIGHT_SOURCES} -) -include_directories(SYSTEM - ${CMAKE_CURRENT_SOURCE_DIR} - ${MOONLIGHT_INCLUDE_DIRS}) -target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC - ${MOONLIGHT_EXTERNAL_LIBRARIES} -) -target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE ${MOONLIGHT_COMPILE_OPTIONS}) -target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE XBOX NXDK) -add_dependencies(${CMAKE_PROJECT_NAME} moonlight-common-c) +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/sources.cmake") -# -# documentation -# if(BUILD_DOCS) add_subdirectory(third-party/doxyconfig docs) endif() -# -# tests -# if(BUILD_TESTS) enable_testing() - add_subdirectory(tests EXCLUDE_FROM_ALL) + add_subdirectory(tests) endif() -# Post-build exe to xbe conversion -add_custom_target(cxbe_convert ALL - VERBATIM COMMAND "${CMAKE_COMMAND}" -E env - ./tools/cxbe/cxbe - -OUT:${XBOX_XBE_DIR}/default.xbe - -TITLE:${XBE_TITLE} - ${CMAKE_CURRENT_BINARY_DIR}/${XBE_TITLE}.exe - WORKING_DIRECTORY ${NXDK_DIR} - COMMENT "CXBE Conversion: [EXE -> XBE]" -) -add_dependencies(cxbe_convert ${CMAKE_PROJECT_NAME}) -add_dependencies(cxbe_convert sync_xbe_assets) - -# Post-build xbe to xiso conversion -add_custom_target(xbe_iso ALL - VERBATIM COMMAND "${CMAKE_COMMAND}" -E env - ${NXDK_DIR}/tools/extract-xiso/build/extract-xiso - -c ${XBOX_XBE_DIR} ${XBOX_ISO} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "CXBE Conversion: [XBE -> XISO]" -) -add_dependencies(xbe_iso cxbe_convert) +if(BUILD_XBOX) + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/nxdk.cmake") -set_target_properties(cxbe_convert PROPERTIES OUTPUT_QUIET ON) -set_target_properties(xbe_iso PROPERTIES OUTPUT_QUIET ON) + moonlight_resolve_nxdk_dir(MOONLIGHT_NXDK_DIR) + moonlight_prepare_nxdk("${MOONLIGHT_NXDK_DIR}" "${CMAKE_BINARY_DIR}/nxdk-bootstrap") + moonlight_add_xbox_build( + NXDK_DIR "${MOONLIGHT_NXDK_DIR}" + BINARY_DIR "${CMAKE_BINARY_DIR}/xbox" + ) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index cb0db9a..0aa380b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,50 +8,32 @@ "configurePresets": [ { "name": "nxdk-release (mingw64)", - "displayName": "nxdk Release (mingw64)", - "description": "Build for Original Xbox using the nxdk toolchain", - "generator": "MSYS Makefiles", - "cmakeExecutable": "C:/msys64/usr/bin/cmake", - "toolchainFile": "${sourceDir}/cmake/toolchain-nxdk-clion.cmake", - "binaryDir": "${sourceDir}/cmake-build-nxdk-release", + "displayName": "Release (mingw64)", + "description": "Build host tests natively with MinGW Makefiles and drive the Xbox target through an internal nxdk child configure", + "generator": "MinGW Makefiles", + "toolchainFile": "${sourceDir}/cmake/host-mingw64-clang.cmake", + "binaryDir": "${sourceDir}/cmake-build-release", "environment": { "CHERE_INVOKING": "1", - "MSYSTEM": "MINGW64", - "NXDK_DIR": "${sourceDir}/third-party/nxdk", - "PATH": "${sourceDir}/cmake;${sourceDir}/third-party/nxdk/bin;C:/msys64/mingw64/bin;C:/msys64/usr/bin;$penv{PATH}" + "MSYSTEM": "MINGW64" }, "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_CXX_COMPILER": "${sourceDir}/cmake/nxdk-cxx.bat", - "CMAKE_C_COMPILER": "${sourceDir}/cmake/nxdk-cc.bat", + "BUILD_DOCS": "ON", + "BUILD_TESTS": "ON", + "BUILD_XBOX": "ON", "CMAKE_DEPENDS_USE_COMPILER": "FALSE", - "CMAKE_MAKE_PROGRAM": "${sourceDir}/cmake/make.bat", - "CMAKE_TRY_COMPILE_TARGET_TYPE": "STATIC_LIBRARY", - "PKG_CONFIG_EXECUTABLE": "${sourceDir}/cmake/nxdk-pkg-config.bat" + "CMAKE_BUILD_TYPE": "Release" } }, { "name": "nxdk-debug (mingw64)", - "displayName": "nxdk Debug (mingw64)", - "description": "Debug build for Original Xbox using the nxdk toolchain", + "displayName": "Debug (mingw64)", + "description": "Debug host build with the internal nxdk Xbox child configure enabled", "inherits": "nxdk-release (mingw64)", "binaryDir": "${sourceDir}/cmake-build-nxdk-debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } - }, - { - "name": "host-tests (mingw64)", - "displayName": "Host Tests (mingw64)", - "description": "Build host-native googletest targets under MSYS2/mingw64", - "generator": "Unix Makefiles", - "cmakeExecutable": "C:/msys64/usr/bin/cmake", - "binaryDir": "${sourceDir}/cmake-build-host-tests", - "cacheVariables": { - "BUILD_DOCS": "OFF", - "BUILD_TESTS": "ON", - "CMAKE_BUILD_TYPE": "Debug" - } } ], "buildPresets": [ @@ -62,22 +44,6 @@ { "name": "nxdk-debug (mingw64)", "configurePreset": "nxdk-debug (mingw64)" - }, - { - "name": "host-tests (mingw64)", - "configurePreset": "host-tests (mingw64)", - "targets": [ - "test_moonlight" - ] - } - ], - "testPresets": [ - { - "name": "host-tests (mingw64)", - "configurePreset": "host-tests (mingw64)", - "output": { - "outputOnFailure": true - } } ] } diff --git a/README.md b/README.md index b6e03c0..b47918d 100644 --- a/README.md +++ b/README.md @@ -91,36 +91,18 @@ dependencies=("${nxdk_dependencies[@]}" "${moonlight_dependencies[@]}") brew install "${dependencies[@]}" ``` -### Pre-Build +### Configure -1. Run the following from mingw64 or bash shell: +Configure the top-level project with the normal host toolchain. CMake uses the vendored `third-party/nxdk` checkout, bootstraps the required `nxdk` outputs, builds the host-native tests with the standard compiler/linker, and drives the Xbox build through an internal child configure that reuses the stock `nxdk` toolchain. ```bash -export NXDK_DIR="$(pwd)/third-party/nxdk" -eval "$(${NXDK_DIR}/bin/activate -s)" -cd "${NXDK_DIR}" -make NXDK_ONLY=y -make tools +cmake -S . -B cmake-build-release -DBUILD_DOCS=OFF -DCMAKE_BUILD_TYPE=Release ``` -### Configure - -1. Create build directory - - ```bash - mkdir -p build - ``` - -2. Configure the project - - ```bash - cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="${NXDK_DIR}/share/toolchain-nxdk.cmake" - ``` - ### Build ```bash -cmake --build build +cmake --build cmake-build-release ``` ### Combined script @@ -131,12 +113,12 @@ This script takes care of everything, except installing the prerequisites. ./build.sh ``` -The default build directory is `build`. You can override it or force a clean build: +The default build directory is `cmake-build-release`. You can override it or force a clean build: ```bash -./build.sh --build-dir cmake-build-nxdk-release +./build.sh --build-dir cmake-build-debug ./build.sh --clean -./build.sh build clean +./build.sh cmake-build-custom clean ``` To launch the same build from shells outside MSYS2 on Windows, use one of these wrappers: @@ -151,28 +133,28 @@ build-mingw64.bat ### Host-native unit tests -The Xbox executable cannot run directly on Windows, macOS, or Linux, so unit tests are built as a separate host-native target. Keep Xbox runtime code thin and move logic you want to test into platform-neutral sources that can be linked into `test_moonlight`. +The Xbox executable cannot run directly on Windows, macOS, or Linux, so the top-level project builds `test_moonlight` natively while the Xbox binary is built by an internal child configure that uses the `nxdk` toolchain. Keep Xbox runtime code thin and move logic you want to test into platform-neutral sources that can be linked into `test_moonlight`. #### Windows via MSYS2/mingw64 -From `cmd.exe`, configure, build, and run the host tests with: +From `cmd.exe`, configure, build, and run the host tests after the helper locates your local MSYS2 installation: ```bat -C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c "cd /c/Users/%USERNAME%/Dev/git/Moonlight-XboxOG && cmake --preset \"host-tests (mingw64)\" && cmake --build --preset \"host-tests (mingw64)\" && ctest --preset \"host-tests (mingw64)\"" +call scripts\find-msys2.cmd && "%MOONLIGHT_MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "cd /c/Users/%USERNAME%/Dev/git/Moonlight-XboxOG && cmake -S . -B cmake-build-host-tests -DBUILD_DOCS=OFF -DBUILD_TESTS=ON -DBUILD_XBOX=OFF -DCMAKE_BUILD_TYPE=Debug && cmake --build cmake-build-host-tests --target test_moonlight && ctest --test-dir cmake-build-host-tests --output-on-failure" ``` If you are already inside a `mingw64` shell, the equivalent commands are: ```bash -cmake --preset "host-tests (mingw64)" -cmake --build --preset "host-tests (mingw64)" -ctest --preset "host-tests (mingw64)" +cmake -S . -B cmake-build-host-tests -DBUILD_DOCS=OFF -DBUILD_TESTS=ON -DBUILD_XBOX=OFF -DCMAKE_BUILD_TYPE=Debug +cmake --build cmake-build-host-tests --target test_moonlight +ctest --test-dir cmake-build-host-tests --output-on-failure ``` #### Linux or macOS ```bash -cmake -S . -B cmake-build-host-tests -DBUILD_TESTS=ON -DBUILD_DOCS=OFF +cmake -S . -B cmake-build-host-tests -DBUILD_TESTS=ON -DBUILD_XBOX=OFF -DBUILD_DOCS=OFF cmake --build cmake-build-host-tests --target test_moonlight ctest --test-dir cmake-build-host-tests --output-on-failure ``` @@ -181,12 +163,12 @@ Coverage should come from this host-native test build instead of the cross-compi ### CLion on Windows -The repository now includes CLion-friendly nxdk wrapper scripts in `cmake/` plus shared run configurations in `.run/`. +The Windows preset in `CMakePresets.json` uses `MinGW Makefiles` for the host-native CLion build, auto-detects the local MSYS2 installation through `cmake/host-mingw64-clang.cmake`, and delegates the Xbox child build through a dedicated CMake driver that enters the vendored `nxdk` environment only for the child configure and build steps. -1. Open the project in CLion and let it import the `nxdk` preset from `CMakePresets.json`. -2. If CLion cached an older failed configure, reload the CMake project or remove `cmake-build-nxdk-release/CMakeCache.txt` once. -3. Use the normal build button with the `nxdk` profile selected. -4. Build `Moonlight.iso` first, then use the shared `Run Moonlight ISO in xemu` run configuration to launch it in xemu. +1. Open the project in CLion and import the `nxdk-release (mingw64)` preset from `CMakePresets.json`. +2. Use the normal build button with that profile selected. The top-level build will compile `test_moonlight` natively and configure the Xbox child build automatically. +3. When you use CLion's **Reset Cache and Reload Project**, the next configure will clean the `nxdk` build outputs and rebuild the required `nxdk` libraries and tools. +4. Build the `moonlight_xbox` target or the default `all` target. The generated ISO now lives at `cmake-build-release/xbox/Moonlight.iso`. For the first xemu launch, you can either run the shared `Setup portable xemu` configuration or run the Windows wrapper manually: @@ -194,9 +176,11 @@ For the first xemu launch, you can either run the shared `Setup portable xemu` c scripts\setup-xemu.cmd ``` -The shared CLion run configurations now call `scripts\setup-xemu.cmd` and `scripts\run-xemu.cmd` directly through `C:\Windows\System32\cmd.exe`. Those wrappers start MSYS2 with the expected `mingw64` environment and then launch the corresponding `.sh` scripts. +The repository now includes `.run/Run xemu.run.xml`, which launches `scripts\run-xemu.cmd` through `C:\Windows\System32\cmd.exe` without extra arguments and lets the launcher auto-discover a built Moonlight ISO. + +If you create a local CLion run configuration that sets the working directory to a build output such as `$CMakeCurrentBuildDir$/xbox`, the Windows wrapper also treats that caller working directory as the xemu target path when no explicit launcher arguments or `MOONLIGHT_XEMU_*` overrides are provided. -The setup script downloads xemu and the emulator support files into `.local/xemu`, then refreshes launcher manifests used by `scripts/run-xemu.sh`. +The setup script downloads xemu and the emulator support files into `.local/xemu`, then refreshes launcher manifests used by `scripts/run-xemu.sh`. The launcher accepts `MOONLIGHT_XEMU_BUILD_DIR`, `MOONLIGHT_XEMU_ISO_PATH`, `--build-dir `, `--iso `, or a single positional path that can point at either a build directory or an ISO file. If you do not pass a path, it falls back across available `cmake-build-*` outputs and prefers the newest built ISO. If you only want the emulator without the ROM/HDD support bundle, run: diff --git a/build-mingw64.bat b/build-mingw64.bat index 3f7c43b..96120b5 100644 --- a/build-mingw64.bat +++ b/build-mingw64.bat @@ -2,15 +2,13 @@ setlocal set "SCRIPT_DIR=%~dp0" -set "MSYS2_SHELL=C:\msys64\msys2_shell.cmd" - -if not exist "%MSYS2_SHELL%" ( - echo MSYS2 shell not found at %MSYS2_SHELL% +call "%SCRIPT_DIR%scripts\find-msys2.cmd" +if errorlevel 1 ( exit /b 1 ) pushd "%SCRIPT_DIR%" >nul -call "%MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "./build.sh %*" +call "%MOONLIGHT_MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "./build.sh %*" set "EXIT_CODE=%ERRORLEVEL%" popd >nul diff --git a/build-mingw64.sh b/build-mingw64.sh index 5213835..b7aec73 100644 --- a/build-mingw64.sh +++ b/build-mingw64.sh @@ -3,7 +3,8 @@ set -eu SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" -MSYS2_SHELL="/c/msys64/msys2_shell.cmd" +MSYS2_ROOT="$(sh "${SCRIPT_DIR}/scripts/find-msys2.sh")" +MSYS2_SHELL="${MSYS2_ROOT}/msys2_shell.cmd" if [ ! -f "${MSYS2_SHELL}" ]; then echo "MSYS2 shell not found at ${MSYS2_SHELL}" diff --git a/build.sh b/build.sh index 490437a..9331d26 100644 --- a/build.sh +++ b/build.sh @@ -9,9 +9,8 @@ usage() { } PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BUILD_DIR="build" +BUILD_DIR="cmake-build-release" CLEAN_BUILD=0 -DISTCLEAN_BUILD=0 POSITIONAL_BUILD_DIR_SET=0 while [[ "$#" -gt 0 ]]; do @@ -30,7 +29,6 @@ while [[ "$#" -gt 0 ]]; do shift ;; --distclean|distclean) - DISTCLEAN_BUILD=1 CLEAN_BUILD=1 shift ;; @@ -61,43 +59,17 @@ case "${BUILD_DIR}" in ;; esac -# Set the NXDK_DIR environment variable -export NXDK_DIR="${NXDK_DIR:-${PROJECT_ROOT}/third-party/nxdk}" - -if [[ ! -d "${NXDK_DIR}" ]]; then - echo "NXDK directory not found: ${NXDK_DIR}" - echo "Run: git submodule update --init --recursive" - exit 1 -fi - -# Activate the nxdk environment -eval "$("${NXDK_DIR}/bin/activate" -s)" - -# Navigate to the nxdk directory -cd "${NXDK_DIR}" - -if [[ "${DISTCLEAN_BUILD}" -eq 1 ]]; then - make distclean -fi - -# Build nxdk with the specified options -make NXDK_ONLY=y -make tools - -cd "${PROJECT_ROOT}" - if [[ "${CLEAN_BUILD}" -eq 1 ]]; then rm -rf "${BUILD_DIR_PATH}" fi -# Configure the project cmake \ - -G "Unix Makefiles" \ - -B "${BUILD_DIR_PATH}" \ -S . \ + -B "${BUILD_DIR_PATH}" \ -DBUILD_DOCS=OFF \ - -DCMAKE_TOOLCHAIN_FILE="${NXDK_DIR}/share/toolchain-nxdk.cmake" \ - -DCMAKE_DEPENDS_USE_COMPILER=FALSE + -DBUILD_TESTS=ON \ + -DBUILD_XBOX=ON \ + -DCMAKE_DEPENDS_USE_COMPILER=FALSE \ + -DCMAKE_BUILD_TYPE=Release -# Build the project cmake --build "${BUILD_DIR_PATH}" diff --git a/cmake/host-mingw64-clang.cmake b/cmake/host-mingw64-clang.cmake new file mode 100644 index 0000000..0abd71c --- /dev/null +++ b/cmake/host-mingw64-clang.cmake @@ -0,0 +1,28 @@ +# Detect the local MSYS2 installation and configure the host-native Windows build +# to use the mingw64 clang toolchain and MinGW Makefiles. + +if(NOT WIN32) + return() +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/msys2.cmake") + +moonlight_detect_windows_msys2_root(_msys2_root) +moonlight_get_windows_msys2_msystem_bin(_msys2_mingw_bin mingw64) +moonlight_get_windows_msys2_usr_bin(_msys2_usr_bin) +moonlight_prepend_windows_msys2_path(_msys2_path mingw64) + +set(ENV{MSYSTEM} "MINGW64") +set(ENV{PATH} "${_msys2_path}") + +find_program(_clang_executable NAMES clang.exe HINTS "${_msys2_mingw_bin}" NO_DEFAULT_PATH REQUIRED) +find_program(_clangxx_executable NAMES clang++.exe HINTS "${_msys2_mingw_bin}" NO_DEFAULT_PATH REQUIRED) +find_program(_mingw_make_executable NAMES mingw32-make.exe HINTS "${_msys2_mingw_bin}" NO_DEFAULT_PATH REQUIRED) + +set(MOONLIGHT_HOST_TOOL_DIR "${_msys2_mingw_bin}" + CACHE PATH "Path to the detected host-native mingw64 tool directory" FORCE +) +set(MOONLIGHT_MSYS2_ROOT "${_msys2_root}" CACHE PATH "Path to the detected MSYS2 installation" FORCE) +set(CMAKE_C_COMPILER "${_clang_executable}" CACHE FILEPATH "Host C compiler for the Windows CLion build" FORCE) +set(CMAKE_CXX_COMPILER "${_clangxx_executable}" CACHE FILEPATH "Host C++ compiler for the Windows CLion build" FORCE) +set(CMAKE_MAKE_PROGRAM "${_mingw_make_executable}" CACHE FILEPATH "Make program for the Windows CLion build" FORCE) diff --git a/cmake/make.bat b/cmake/make.bat deleted file mode 100644 index 6950055..0000000 --- a/cmake/make.bat +++ /dev/null @@ -1,22 +0,0 @@ -@echo off -setlocal EnableExtensions - -set "MSYS2_SHELL=C:\msys64\msys2_shell.cmd" - -if not exist "%MSYS2_SHELL%" ( - echo MSYS2 shell not found at %MSYS2_SHELL%>&2 - exit /b 1 -) - -set "MAKE_ARGS=" -:collect_args -if "%~1"=="" goto run_make -set "ARG=%~1" -set "ARG=%ARG:'='\''%" -set "MAKE_ARGS=%MAKE_ARGS% '%ARG%'" -shift -goto collect_args - -:run_make -call "%MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "make%MAKE_ARGS%" -exit /b %ERRORLEVEL% diff --git a/cmake/msys2.cmake b/cmake/msys2.cmake new file mode 100644 index 0000000..4065649 --- /dev/null +++ b/cmake/msys2.cmake @@ -0,0 +1,184 @@ +# Detect the Windows MSYS2 installation and expose the paths needed by host and nxdk builds. + +include_guard(GLOBAL) + +# Normalize a Windows path to cmake-style forward slashes with no trailing slash. +function(_moonlight_normalize_windows_path out_var input_path) + if("${input_path}" STREQUAL "") + set(${out_var} "" PARENT_SCOPE) + return() + endif() + + file(TO_CMAKE_PATH "${input_path}" _normalized_path) + string(REGEX REPLACE "/$" "" _normalized_path "${_normalized_path}") + set(${out_var} "${_normalized_path}" PARENT_SCOPE) +endfunction() + +# Validate a candidate MSYS2 root path and set the output variable when valid. +function(_moonlight_set_msys2_root_if_valid out_var candidate_root) + _moonlight_normalize_windows_path(_normalized_root "${candidate_root}") + + if(NOT _normalized_root STREQUAL "" AND EXISTS "${_normalized_root}/msys2_shell.cmd") + set(${out_var} "${_normalized_root}" PARENT_SCOPE) + else() + set(${out_var} "" PARENT_SCOPE) + endif() +endfunction() + +# Attempt to derive the MSYS2 root directory by walking up from a known tool path. +function(_moonlight_try_msys2_root_from_tool out_var tool_path) + if("${tool_path}" STREQUAL "") + set(${out_var} "" PARENT_SCOPE) + return() + endif() + + _moonlight_normalize_windows_path(_tool_path "${tool_path}") + get_filename_component(_tool_dir "${_tool_path}" DIRECTORY) + get_filename_component(_tool_name "${_tool_path}" NAME) + + set(candidate_roots) + if(_tool_name STREQUAL "msys2_shell.cmd") + list(APPEND candidate_roots "${_tool_dir}") + endif() + + get_filename_component(_one_up "${_tool_dir}/.." ABSOLUTE) + get_filename_component(_two_up "${_tool_dir}/../.." ABSOLUTE) + list(APPEND candidate_roots "${_one_up}" "${_two_up}") + + foreach(candidate_root IN LISTS candidate_roots) + _moonlight_set_msys2_root_if_valid(_resolved_root "${candidate_root}") + if(NOT _resolved_root STREQUAL "") + set(${out_var} "${_resolved_root}" PARENT_SCOPE) + return() + endif() + endforeach() + + set(${out_var} "" PARENT_SCOPE) +endfunction() + +# Detect the MSYS2 installation root on Windows and cache the resolved path. +function(moonlight_detect_windows_msys2_root out_var) + if(NOT WIN32) + message(FATAL_ERROR "moonlight_detect_windows_msys2_root is only available on Windows hosts") + endif() + + set(candidate_roots) + if(DEFINED MOONLIGHT_MSYS2_ROOT AND NOT MOONLIGHT_MSYS2_ROOT STREQUAL "") + list(APPEND candidate_roots "${MOONLIGHT_MSYS2_ROOT}") + endif() + if(DEFINED ENV{MSYS2_ROOT} AND NOT "$ENV{MSYS2_ROOT}" STREQUAL "") + list(APPEND candidate_roots "$ENV{MSYS2_ROOT}") + endif() + if(DEFINED ENV{SystemDrive} AND NOT "$ENV{SystemDrive}" STREQUAL "") + list(APPEND candidate_roots "$ENV{SystemDrive}/msys64") + endif() + list(APPEND candidate_roots + "C:/msys64" + "C:/tools/msys64" + ) + + foreach(candidate_root IN LISTS candidate_roots) + _moonlight_set_msys2_root_if_valid(_resolved_root "${candidate_root}") + if(NOT _resolved_root STREQUAL "") + set(MOONLIGHT_MSYS2_ROOT "${_resolved_root}" CACHE PATH "Path to the detected MSYS2 installation" FORCE) + set(${out_var} "${_resolved_root}" PARENT_SCOPE) + return() + endif() + endforeach() + + set(program_hints ${candidate_roots}) + + find_program(_msys2_shell_path + NAMES msys2_shell.cmd + HINTS ${program_hints} + PATH_SUFFIXES . + ) + _moonlight_try_msys2_root_from_tool(_resolved_root "${_msys2_shell_path}") + if(NOT _resolved_root STREQUAL "") + set(MOONLIGHT_MSYS2_ROOT "${_resolved_root}" CACHE PATH "Path to the detected MSYS2 installation" FORCE) + set(${out_var} "${_resolved_root}" PARENT_SCOPE) + return() + endif() + + foreach(tool_name bash.exe mingw32-make.exe clang++.exe clang.exe) + find_program(_tool_path + NAMES ${tool_name} + HINTS ${program_hints} + PATH_SUFFIXES usr/bin mingw64/bin ucrt64/bin clang64/bin clangarm64/bin mingw32/bin + ) + _moonlight_try_msys2_root_from_tool(_resolved_root "${_tool_path}") + if(NOT _resolved_root STREQUAL "") + set(MOONLIGHT_MSYS2_ROOT "${_resolved_root}" CACHE PATH "Path to the detected MSYS2 installation" FORCE) + set(${out_var} "${_resolved_root}" PARENT_SCOPE) + return() + endif() + endforeach() + + message(FATAL_ERROR + "Could not find an MSYS2 installation. " + "Set the MSYS2_ROOT environment variable or add the MSYS2 tools to PATH.") +endfunction() + +# Get the path to the MSYS2 shell script. +function(moonlight_get_windows_msys2_shell out_var) + moonlight_detect_windows_msys2_root(_msys2_root) + set(msys2_shell "${_msys2_root}/msys2_shell.cmd") + set(${out_var} "${msys2_shell}" PARENT_SCOPE) +endfunction() + +# Get the MSYS2 usr/bin directory path. +function(moonlight_get_windows_msys2_usr_bin out_var) + moonlight_detect_windows_msys2_root(_msys2_root) + set(msys2_usr_bin "${_msys2_root}/usr/bin") + set(${out_var} "${msys2_usr_bin}" PARENT_SCOPE) +endfunction() + +# Get the path to the MSYS2 bash executable, failing if it does not exist. +function(moonlight_get_windows_msys2_bash out_var) + moonlight_get_windows_msys2_usr_bin(_msys2_usr_bin) + set(bash_executable "${_msys2_usr_bin}/bash.exe") + + if(NOT EXISTS "${bash_executable}") + message(FATAL_ERROR "MSYS2 bash not found at ${bash_executable}") + endif() + + set(${out_var} "${bash_executable}" PARENT_SCOPE) +endfunction() + +# Get the bin directory for the specified MSYS2 MSYSTEM environment (default: mingw64). +function(moonlight_get_windows_msys2_msystem_bin out_var) + set(msystem_dir "mingw64") + if(ARGC GREATER 1 AND NOT "${ARGV1}" STREQUAL "") + set(msystem_dir "${ARGV1}") + endif() + + moonlight_detect_windows_msys2_root(_msys2_root) + set(msystem_bin "${_msys2_root}/${msystem_dir}/bin") + + if(NOT EXISTS "${msystem_bin}") + message(FATAL_ERROR "MSYS2 ${msystem_dir} bin directory not found at ${msystem_bin}") + endif() + + set(${out_var} "${msystem_bin}" PARENT_SCOPE) +endfunction() + +# Build a PATH-style string prepending the MSYS2 MSYSTEM and usr/bin directories. +function(moonlight_prepend_windows_msys2_path out_var) + set(msystem_dir "mingw64") + if(ARGC GREATER 1 AND NOT "${ARGV1}" STREQUAL "") + set(msystem_dir "${ARGV1}") + endif() + + moonlight_get_windows_msys2_msystem_bin(_msystem_bin "${msystem_dir}") + moonlight_get_windows_msys2_usr_bin(_msys2_usr_bin) + + set(path_entries "${_msystem_bin}" "${_msys2_usr_bin}") + file(TO_CMAKE_PATH "$ENV{PATH}" _existing_path) + if(NOT _existing_path STREQUAL "") + list(APPEND path_entries ${_existing_path}) + endif() + + list(REMOVE_DUPLICATES path_entries) + list(JOIN path_entries ";" resolved_path) + set(${out_var} "${resolved_path}" PARENT_SCOPE) +endfunction() diff --git a/cmake/nxdk-cc.bat b/cmake/nxdk-cc.bat deleted file mode 100644 index b84b968..0000000 --- a/cmake/nxdk-cc.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -setlocal - -set "SCRIPT_DIR=%~dp0" -set "PROJECT_ROOT=%SCRIPT_DIR%.." -set "NXDK_DIR=%PROJECT_ROOT%\third-party\nxdk" -set "BASH_EXE=C:\msys64\usr\bin\bash.exe" -set "HELPER_SCRIPT=%SCRIPT_DIR%run-nxdk-tool.sh" - -if not exist "%BASH_EXE%" ( - echo MSYS2 bash not found at %BASH_EXE%>&2 - exit /b 1 -) - -if not exist "%NXDK_DIR%\bin\nxdk-cc" ( - echo nxdk compiler wrapper not found at %NXDK_DIR%\bin\nxdk-cc>&2 - exit /b 1 -) - -"%BASH_EXE%" "%HELPER_SCRIPT%" "%NXDK_DIR%" nxdk-cc %* -exit /b %ERRORLEVEL% diff --git a/cmake/nxdk-cxx.bat b/cmake/nxdk-cxx.bat deleted file mode 100644 index a9c2219..0000000 --- a/cmake/nxdk-cxx.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -setlocal - -set "SCRIPT_DIR=%~dp0" -set "PROJECT_ROOT=%SCRIPT_DIR%.." -set "NXDK_DIR=%PROJECT_ROOT%\third-party\nxdk" -set "BASH_EXE=C:\msys64\usr\bin\bash.exe" -set "HELPER_SCRIPT=%SCRIPT_DIR%run-nxdk-tool.sh" - -if not exist "%BASH_EXE%" ( - echo MSYS2 bash not found at %BASH_EXE%>&2 - exit /b 1 -) - -if not exist "%NXDK_DIR%\bin\nxdk-cxx" ( - echo nxdk compiler wrapper not found at %NXDK_DIR%\bin\nxdk-cxx>&2 - exit /b 1 -) - -"%BASH_EXE%" "%HELPER_SCRIPT%" "%NXDK_DIR%" nxdk-cxx %* -exit /b %ERRORLEVEL% diff --git a/cmake/nxdk-link.bat b/cmake/nxdk-link.bat deleted file mode 100644 index 3ff5970..0000000 --- a/cmake/nxdk-link.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -setlocal - -set "SCRIPT_DIR=%~dp0" -set "PROJECT_ROOT=%SCRIPT_DIR%.." -set "NXDK_DIR=%PROJECT_ROOT%\third-party\nxdk" -set "BASH_EXE=C:\msys64\usr\bin\bash.exe" -set "HELPER_SCRIPT=%SCRIPT_DIR%run-nxdk-tool.sh" - -if not exist "%BASH_EXE%" ( - echo MSYS2 bash not found at %BASH_EXE%>&2 - exit /b 1 -) - -if not exist "%NXDK_DIR%\bin\nxdk-link" ( - echo nxdk linker wrapper not found at %NXDK_DIR%\bin\nxdk-link>&2 - exit /b 1 -) - -"%BASH_EXE%" "%HELPER_SCRIPT%" "%NXDK_DIR%" nxdk-link %* -exit /b %ERRORLEVEL% diff --git a/cmake/nxdk-pkg-config.bat b/cmake/nxdk-pkg-config.bat deleted file mode 100644 index 807495b..0000000 --- a/cmake/nxdk-pkg-config.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -setlocal - -set "SCRIPT_DIR=%~dp0" -set "PROJECT_ROOT=%SCRIPT_DIR%.." -set "NXDK_DIR=%PROJECT_ROOT%\third-party\nxdk" -set "BASH_EXE=C:\msys64\usr\bin\bash.exe" -set "HELPER_SCRIPT=%SCRIPT_DIR%run-nxdk-tool.sh" - -if not exist "%BASH_EXE%" ( - echo MSYS2 bash not found at %BASH_EXE%>&2 - exit /b 1 -) - -if not exist "%NXDK_DIR%\bin\nxdk-pkg-config" ( - echo nxdk pkg-config wrapper not found at %NXDK_DIR%\bin\nxdk-pkg-config>&2 - exit /b 1 -) - -"%BASH_EXE%" "%HELPER_SCRIPT%" "%NXDK_DIR%" nxdk-pkg-config %* -exit /b %ERRORLEVEL% diff --git a/cmake/nxdk.cmake b/cmake/nxdk.cmake new file mode 100644 index 0000000..e65655b --- /dev/null +++ b/cmake/nxdk.cmake @@ -0,0 +1,358 @@ +# Resolve the vendored nxdk checkout, bootstrap its prerequisites, and drive the internal Xbox child build. + +include_guard(GLOBAL) + +include(CMakeParseArguments) +include("${CMAKE_CURRENT_LIST_DIR}/msys2.cmake") + +set(MOONLIGHT_NXDK_DIR "${CMAKE_SOURCE_DIR}/third-party/nxdk" CACHE PATH "Path to the vendored nxdk checkout") +cmake_path(ABSOLUTE_PATH MOONLIGHT_NXDK_DIR BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" NORMALIZE) + +# Resolve and validate the nxdk directory, returning its absolute path. +function(moonlight_resolve_nxdk_dir out_var) + set(nxdk_dir "${MOONLIGHT_NXDK_DIR}") + cmake_path(ABSOLUTE_PATH nxdk_dir BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" NORMALIZE) + + if(NOT EXISTS "${nxdk_dir}/bin/activate") + message(FATAL_ERROR + "NXDK directory not found: ${nxdk_dir}\n" + "Run: git submodule update --init --recursive") + endif() + + set(${out_var} "${nxdk_dir}" PARENT_SCOPE) +endfunction() + +# Get the path to the MSYS2 shell script for use in Xbox build commands. +function(_moonlight_get_windows_msys2_shell out_var) + moonlight_get_windows_msys2_shell(_msys2_shell) + set(${out_var} "${_msys2_shell}" PARENT_SCOPE) +endfunction() + +# Locate the make program appropriate for driving the Xbox nxdk build. +function(_moonlight_get_xbox_make_program out_var) + if(WIN32) + moonlight_get_windows_msys2_usr_bin(_msys2_usr_bin) + set(make_program "${_msys2_usr_bin}/make.exe") + if(NOT EXISTS "${make_program}") + message(FATAL_ERROR "MSYS2 make not found at ${make_program}") + endif() + else() + find_program(make_program NAMES make REQUIRED) + endif() + + set(${out_var} "${make_program}" PARENT_SCOPE) +endfunction() + +# Convert a native Windows path to an MSYS2 POSIX-style path. +function(_moonlight_to_msys_path out_var path) + file(TO_CMAKE_PATH "${path}" normalized_path) + + if(normalized_path MATCHES "^([A-Za-z]):/(.*)$") + string(TOLOWER "${CMAKE_MATCH_1}" _drive) + set(normalized_path "/${_drive}/${CMAKE_MATCH_2}") + endif() + + set(${out_var} "${normalized_path}" PARENT_SCOPE) +endfunction() + +# Single-quote a value for safe embedding in a POSIX shell command string. +function(_moonlight_shell_quote out_var value) + string(REPLACE "'" "'\"'\"'" _escaped_value "${value}") + set(${out_var} "'${_escaped_value}'" PARENT_SCOPE) +endfunction() + +# Join a list of arguments into a single-quoted shell command string. +function(_moonlight_join_shell_command out_var) + set(quoted_args) + + foreach(arg IN LISTS ARGN) + _moonlight_shell_quote(_quoted_arg "${arg}") + list(APPEND quoted_args "${_quoted_arg}") + endforeach() + + list(JOIN quoted_args " " command) + set(${out_var} "${command}" PARENT_SCOPE) +endfunction() + +# Build the PATH string needed to run nxdk build commands on the current platform. +function(_moonlight_get_nxdk_path out_var nxdk_dir) + set(path_entries "${nxdk_dir}/bin") + + if(APPLE) + foreach(llvm_path "/opt/homebrew/opt/llvm/bin" "/usr/local/opt/llvm/bin") + if(EXISTS "${llvm_path}/clang") + list(APPEND path_entries "${llvm_path}") + endif() + endforeach() + endif() + + if(WIN32) + moonlight_get_windows_msys2_msystem_bin(_msys2_mingw_bin mingw64) + moonlight_get_windows_msys2_usr_bin(_msys2_usr_bin) + list(APPEND path_entries "${_msys2_mingw_bin}" "${_msys2_usr_bin}") + file(TO_CMAKE_PATH "$ENV{PATH}" _existing_path) + if(NOT _existing_path STREQUAL "") + list(APPEND path_entries ${_existing_path}) + endif() + set(path_separator ";") + else() + if(NOT "$ENV{PATH}" STREQUAL "") + string(REPLACE ":" ";" _existing_path "$ENV{PATH}") + list(APPEND path_entries ${_existing_path}) + endif() + set(path_separator ":") + endif() + + list(JOIN path_entries "${path_separator}" nxdk_path) + set(${out_var} "${nxdk_path}" PARENT_SCOPE) +endfunction() + +# Execute a command inside the nxdk environment, sourcing the NXDK activation on all platforms. +function(moonlight_run_nxdk_command description nxdk_dir working_directory) + if(WIN32) + _moonlight_get_windows_msys2_shell(_msys2_shell) + _moonlight_to_msys_path(_msys_nxdk_dir "${nxdk_dir}") + _moonlight_to_msys_path(_msys_working_directory "${working_directory}") + _moonlight_join_shell_command(_shell_command ${ARGN}) + _moonlight_shell_quote(_quoted_nxdk_dir "${_msys_nxdk_dir}") + _moonlight_shell_quote(_quoted_working_directory "${_msys_working_directory}") + + string(CONCAT _shell_script + "unset MAKEFLAGS MFLAGS GNUMAKEFLAGS MAKELEVEL; " + "export NXDK_DIR=${_quoted_nxdk_dir}; " + "export PATH=\"$NXDK_DIR/bin:$PATH\"; " + "cd ${_quoted_working_directory}; " + "exec ${_shell_command}" + ) + + execute_process( + COMMAND "${_msys2_shell}" -defterm -here -no-start -mingw64 -c "${_shell_script}" + RESULT_VARIABLE _moonlight_command_result + ) + else() + _moonlight_get_nxdk_path(_moonlight_nxdk_path "${nxdk_dir}") + + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + "NXDK_DIR=${nxdk_dir}" + "PATH=${_moonlight_nxdk_path}" + ${ARGN} + WORKING_DIRECTORY "${working_directory}" + RESULT_VARIABLE _moonlight_command_result + ) + endif() + + if(NOT _moonlight_command_result EQUAL 0) + message(FATAL_ERROR "${description} failed with exit code ${_moonlight_command_result}") + endif() +endfunction() + +# Run make inside the nxdk environment with the platform-appropriate make tool. +function(_moonlight_run_nxdk_make nxdk_dir description) + if(WIN32) + set(make_program make) + else() + _moonlight_get_xbox_make_program(make_program) + endif() + + moonlight_run_nxdk_command("${description}" "${nxdk_dir}" "${nxdk_dir}" "${make_program}" ${ARGN}) +endfunction() + +# Report whether any listed output path is missing. +function(_moonlight_has_missing_output out_var) + set(missing_output FALSE) + + foreach(required_output IN LISTS ARGN) + if(NOT EXISTS "${required_output}") + set(missing_output TRUE) + break() + endif() + endforeach() + + set(${out_var} "${missing_output}" PARENT_SCOPE) +endfunction() + +# Build the vendored cxbe tool when it is missing. +function(_moonlight_prepare_cxbe nxdk_dir cxbe_path) + if(EXISTS "${cxbe_path}") + return() + endif() + + message(STATUS "Preparing cxbe at ${nxdk_dir}") + if(WIN32) + moonlight_run_nxdk_command("cxbe build" "${nxdk_dir}" "${nxdk_dir}/tools/cxbe" make) + return() + endif() + + _moonlight_get_xbox_make_program(host_make_program) + moonlight_run_nxdk_command("cxbe build" "${nxdk_dir}" "${nxdk_dir}/tools/cxbe" "${host_make_program}") +endfunction() + +# Configure and build the vendored extract-xiso tool when it is missing. +function(_moonlight_prepare_extract_xiso nxdk_dir source_dir build_dir output_path) + if(EXISTS "${output_path}") + return() + endif() + + message(STATUS "Preparing extract-xiso at ${nxdk_dir}") + file(REMOVE_RECURSE "${build_dir}") + file(MAKE_DIRECTORY "${build_dir}") + moonlight_run_nxdk_command( + "extract-xiso configure" + "${nxdk_dir}" + "${nxdk_dir}" + "${CMAKE_COMMAND}" + -S "${source_dir}" + -B "${build_dir}" + -G "Unix Makefiles" + ) + moonlight_run_nxdk_command( + "extract-xiso build" + "${nxdk_dir}" + "${nxdk_dir}" + "${CMAKE_COMMAND}" + --build "${build_dir}" + ) +endfunction() + +# Build the vendored host-side nxdk tools required by the Moonlight Xbox build. +function(_moonlight_prepare_nxdk_tools nxdk_dir cxbe_path source_dir build_dir output_path) + _moonlight_prepare_cxbe("${nxdk_dir}" "${cxbe_path}") + _moonlight_prepare_extract_xiso( + "${nxdk_dir}" + "${source_dir}" + "${build_dir}" + "${output_path}" + ) +endfunction() + +# Bootstrap the vendored nxdk outputs required by the host-first Moonlight build. +function(moonlight_prepare_nxdk nxdk_dir state_dir) + file(MAKE_DIRECTORY "${state_dir}") + + set(signature_inputs + "NXDK_DIR=${nxdk_dir}" + "HOST_SYSTEM=${CMAKE_HOST_SYSTEM_NAME}" + "NXDK_TOOLCHAIN=${nxdk_dir}/share/toolchain-nxdk.cmake" + "NXDK_ENV_MODE=cmake-driver" + ) + list(JOIN signature_inputs "\n" signature_text) + string(SHA256 signature "${signature_text}") + + set(signature_file "${state_dir}/bootstrap.signature") + set(cxbe_path "${nxdk_dir}/tools/cxbe/cxbe") + set(extract_xiso_source_dir "${nxdk_dir}/tools/extract-xiso") + set(extract_xiso_build_dir "${extract_xiso_source_dir}/build") + set(extract_xiso_path "${extract_xiso_build_dir}/extract-xiso") + set(required_libraries + "${nxdk_dir}/lib/libnxdk.lib" + "${nxdk_dir}/lib/libc++.lib" + "${nxdk_dir}/lib/libSDL2.lib" + "${nxdk_dir}/lib/libSDL2_image.lib" + ) + set(required_tools + "${cxbe_path}" + "${extract_xiso_path}" + ) + + if(MOONLIGHT_FORCE_NXDK_DISTCLEAN) + set(need_distclean TRUE) + else() + set(need_distclean FALSE) + endif() + + if(NOT EXISTS "${signature_file}") + set(need_distclean TRUE) + else() + file(READ "${signature_file}" saved_signature) + string(STRIP "${saved_signature}" saved_signature) + if(NOT saved_signature STREQUAL signature) + set(need_distclean TRUE) + endif() + endif() + + _moonlight_has_missing_output(need_prepare_libraries ${required_libraries}) + _moonlight_has_missing_output(need_prepare_tools ${required_tools}) + + if(need_distclean) + message(STATUS "Cleaning nxdk build tree at ${nxdk_dir}") + _moonlight_run_nxdk_make("${nxdk_dir}" "nxdk clean" clean) + set(need_prepare_libraries TRUE) + endif() + + if(need_prepare_libraries) + message(STATUS "Preparing nxdk libraries at ${nxdk_dir}") + _moonlight_run_nxdk_make("${nxdk_dir}" "nxdk bootstrap" NXDK_ONLY=y) + else() + message(STATUS "Using existing nxdk library outputs from ${nxdk_dir}") + endif() + + if(need_prepare_tools) + _moonlight_prepare_nxdk_tools( + "${nxdk_dir}" + "${cxbe_path}" + "${extract_xiso_source_dir}" + "${extract_xiso_build_dir}" + "${extract_xiso_path}" + ) + else() + message(STATUS "Using existing nxdk tool outputs from ${nxdk_dir}") + endif() + + file(WRITE "${signature_file}" "${signature}\n") +endfunction() + +# Configure the internal Xbox child build and expose it as the moonlight_xbox target. +function(moonlight_add_xbox_build) + set(options) + set(oneValueArgs BINARY_DIR NXDK_DIR) + cmake_parse_arguments(MOONLIGHT_XBOX "${options}" "${oneValueArgs}" "" ${ARGN}) + + if(NOT MOONLIGHT_XBOX_BINARY_DIR) + message(FATAL_ERROR "moonlight_add_xbox_build requires BINARY_DIR") + endif() + + if(NOT MOONLIGHT_XBOX_NXDK_DIR) + message(FATAL_ERROR "moonlight_add_xbox_build requires NXDK_DIR") + endif() + + _moonlight_get_xbox_make_program(xbox_make_program) + + set(toolchain_file "${MOONLIGHT_XBOX_NXDK_DIR}/share/toolchain-nxdk.cmake") + set(driver_script "${CMAKE_SOURCE_DIR}/cmake/run-child-build.cmake") + set(configure_command + "${CMAKE_COMMAND}" + -DMOONLIGHT_COMMAND_MODE:STRING=configure + -DMOONLIGHT_CMAKE_COMMAND:FILEPATH=${CMAKE_COMMAND} + -DMOONLIGHT_DESCRIPTION:STRING=Configure Xbox child build + -DMOONLIGHT_NXDK_DIR:PATH=${MOONLIGHT_XBOX_NXDK_DIR} + -DMOONLIGHT_SOURCE_DIR:PATH=${CMAKE_SOURCE_DIR} + -DMOONLIGHT_BINARY_DIR:PATH=${MOONLIGHT_XBOX_BINARY_DIR} + -DMOONLIGHT_WORKING_DIRECTORY:PATH=${CMAKE_SOURCE_DIR} + -DMOONLIGHT_MAKE_PROGRAM:FILEPATH=${xbox_make_program} + -DMOONLIGHT_TOOLCHAIN_FILE:FILEPATH=${toolchain_file} + -DMOONLIGHT_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -P "${driver_script}" + ) + set(build_command + "${CMAKE_COMMAND}" + -DMOONLIGHT_COMMAND_MODE:STRING=build + -DMOONLIGHT_CMAKE_COMMAND:FILEPATH=${CMAKE_COMMAND} + -DMOONLIGHT_DESCRIPTION:STRING=Build Xbox child target + -DMOONLIGHT_NXDK_DIR:PATH=${MOONLIGHT_XBOX_NXDK_DIR} + -DMOONLIGHT_BINARY_DIR:PATH=${MOONLIGHT_XBOX_BINARY_DIR} + -DMOONLIGHT_WORKING_DIRECTORY:PATH=${MOONLIGHT_XBOX_BINARY_DIR} + -P "${driver_script}" + ) + + add_custom_target(moonlight_xbox ALL + COMMAND ${configure_command} + COMMAND ${build_command} + BYPRODUCTS + "${MOONLIGHT_XBOX_BINARY_DIR}/Moonlight.iso" + "${MOONLIGHT_XBOX_BINARY_DIR}/xbe/default.xbe" + COMMENT "Configure and build the Xbox target in ${MOONLIGHT_XBOX_BINARY_DIR}" + USES_TERMINAL + VERBATIM + ) +endfunction() diff --git a/cmake/run-child-build.cmake b/cmake/run-child-build.cmake new file mode 100644 index 0000000..f3a7a18 --- /dev/null +++ b/cmake/run-child-build.cmake @@ -0,0 +1,56 @@ +# Configure or build the internal Xbox child project inside the vendored nxdk environment. + +foreach(required_var + MOONLIGHT_COMMAND_MODE + MOONLIGHT_CMAKE_COMMAND + MOONLIGHT_DESCRIPTION + MOONLIGHT_NXDK_DIR + MOONLIGHT_BINARY_DIR + MOONLIGHT_WORKING_DIRECTORY) + if(NOT DEFINED ${required_var} OR "${${required_var}}" STREQUAL "") + message(FATAL_ERROR "${required_var} must be defined when running run-child-build.cmake") + endif() +endforeach() + +include("${CMAKE_CURRENT_LIST_DIR}/nxdk.cmake") + +if(MOONLIGHT_COMMAND_MODE STREQUAL "configure") + foreach(required_var MOONLIGHT_SOURCE_DIR MOONLIGHT_MAKE_PROGRAM MOONLIGHT_TOOLCHAIN_FILE) + if(NOT DEFINED ${required_var} OR "${${required_var}}" STREQUAL "") + message(FATAL_ERROR "${required_var} must be defined for configure mode") + endif() + endforeach() + + set(_command_args + "${MOONLIGHT_CMAKE_COMMAND}" + -G "Unix Makefiles" + "-DCMAKE_MAKE_PROGRAM:FILEPATH=${MOONLIGHT_MAKE_PROGRAM}" + -S "${MOONLIGHT_SOURCE_DIR}" + -B "${MOONLIGHT_BINARY_DIR}" + -DMOONLIGHT_BUILD_KIND:STRING=XBOX + -DMOONLIGHT_SKIP_NXDK_PREP:BOOL=ON + "-DNXDK_DIR:PATH=${MOONLIGHT_NXDK_DIR}" + -DBUILD_DOCS:BOOL=OFF + "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${MOONLIGHT_TOOLCHAIN_FILE}" + -DCMAKE_DEPENDS_USE_COMPILER:BOOL=FALSE + -DCMAKE_TRY_COMPILE_TARGET_TYPE:STRING=STATIC_LIBRARY + ) + + if(DEFINED MOONLIGHT_BUILD_TYPE AND NOT MOONLIGHT_BUILD_TYPE STREQUAL "") + list(APPEND _command_args "-DCMAKE_BUILD_TYPE:STRING=${MOONLIGHT_BUILD_TYPE}") + endif() +elseif(MOONLIGHT_COMMAND_MODE STREQUAL "build") + set(_command_args + "${MOONLIGHT_CMAKE_COMMAND}" + --build "${MOONLIGHT_BINARY_DIR}" + ) +else() + message(FATAL_ERROR "Unsupported MOONLIGHT_COMMAND_MODE: ${MOONLIGHT_COMMAND_MODE}") +endif() + +moonlight_run_nxdk_command( + "${MOONLIGHT_DESCRIPTION}" + "${MOONLIGHT_NXDK_DIR}" + "${MOONLIGHT_WORKING_DIRECTORY}" + ${_command_args} +) diff --git a/cmake/run-nxdk-tool.sh b/cmake/run-nxdk-tool.sh deleted file mode 100644 index 9660eb0..0000000 --- a/cmake/run-nxdk-tool.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [[ $# -lt 2 ]]; then - printf 'Usage: %s [args...]\n' "$0" >&2 - exit 2 -fi - -export PATH="/mingw64/bin:/usr/bin:${PATH}" - -WRAPPER_DIR="$(cd "$(dirname "$0")" && pwd)" -NXDK_DIR="$(cygpath -u "$1")" -TOOL_NAME="$2" -shift 2 - -export MSYSTEM="MINGW64" -export CHERE_INVOKING="1" -export NXDK_DIR -export PATH="${WRAPPER_DIR}:${NXDK_DIR}/bin:${PATH}" - -exec "${NXDK_DIR}/bin/${TOOL_NAME}" "$@" diff --git a/cmake/sources.cmake b/cmake/sources.cmake new file mode 100644 index 0000000..23a8380 --- /dev/null +++ b/cmake/sources.cmake @@ -0,0 +1,21 @@ +# Define the shared Moonlight source lists used by both the host-native test build +# and the Xbox child build, including the sources that must stay out of host tests. + +include_guard(GLOBAL) + +set(MOONLIGHT_SOURCE_ROOT "${CMAKE_SOURCE_DIR}") + +file(GLOB_RECURSE MOONLIGHT_SOURCES CONFIGURE_DEPENDS + "${MOONLIGHT_SOURCE_ROOT}/src/*.cpp" +) + +set(MOONLIGHT_TEST_EXCLUDED_SOURCES + "${MOONLIGHT_SOURCE_ROOT}/src/main.cpp" + "${MOONLIGHT_SOURCE_ROOT}/src/splash/splash_screen.cpp" + "${MOONLIGHT_SOURCE_ROOT}/src/startup/memory_stats.cpp" +) + +set(MOONLIGHT_HOST_TESTABLE_SOURCES ${MOONLIGHT_SOURCES}) +list(REMOVE_ITEM MOONLIGHT_HOST_TESTABLE_SOURCES ${MOONLIGHT_TEST_EXCLUDED_SOURCES}) + +set(MOONLIGHT_COMPILE_OPTIONS -Wno-builtin-macro-redefined) diff --git a/cmake/toolchain-nxdk-clion.cmake b/cmake/toolchain-nxdk-clion.cmake deleted file mode 100644 index a848642..0000000 --- a/cmake/toolchain-nxdk-clion.cmake +++ /dev/null @@ -1,7 +0,0 @@ -include("${CMAKE_CURRENT_LIST_DIR}/../third-party/nxdk/share/toolchain-nxdk.cmake") - -if(CMAKE_HOST_WIN32) - set(CMAKE_C_COMPILER "${CMAKE_CURRENT_LIST_DIR}/nxdk-cc.bat" CACHE FILEPATH "" FORCE) - set(CMAKE_CXX_COMPILER "${CMAKE_CURRENT_LIST_DIR}/nxdk-cxx.bat" CACHE FILEPATH "" FORCE) - set(PKG_CONFIG_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/nxdk-pkg-config.bat" CACHE STRING "Path to pkg-config" FORCE) -endif() diff --git a/cmake/xbox-build.cmake b/cmake/xbox-build.cmake new file mode 100644 index 0000000..42ca951 --- /dev/null +++ b/cmake/xbox-build.cmake @@ -0,0 +1,98 @@ +# Build the Xbox child project with the stock nxdk toolchain, then package the +# resulting executable into the XBE and ISO artifacts consumed by xemu and CI. + +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/sources.cmake") +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/nxdk.cmake") + +set(XBE_TITLE ${CMAKE_PROJECT_NAME}) +set(XBOX_XBE_DIR "${CMAKE_CURRENT_BINARY_DIR}/xbe") +set(XBOX_ISO_NAME "${CMAKE_PROJECT_NAME}.iso") +set(XBOX_ISO "${CMAKE_CURRENT_BINARY_DIR}/${XBOX_ISO_NAME}") + +moonlight_resolve_nxdk_dir(NXDK_DIR) +set(ENV{NXDK_DIR} "${NXDK_DIR}") + +if(NOT MOONLIGHT_SKIP_NXDK_PREP) + moonlight_prepare_nxdk("${NXDK_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/nxdk-bootstrap") +endif() + +find_package(NXDK REQUIRED) +find_package(NXDK_SDL2 REQUIRED) +find_package(NXDK_SDL2_Image REQUIRED) + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -include:_automount_d_drive") + +file(MAKE_DIRECTORY "${XBOX_XBE_DIR}") + +add_custom_target(sync_xbe_assets ALL + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/xbe" + "${XBOX_XBE_DIR}" + COMMENT "Sync XBE assets" +) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +set(CMAKE_CXX_FLAGS_RELEASE "-O2") +set(CMAKE_C_FLAGS_RELEASE "-O2") + +include(GetOpenSSL REQUIRED) +set(ENET_NO_INSTALL ON CACHE BOOL "Do not install libraries built for enet" FORCE) +set(BUILD_SHARED_LIBS OFF) +add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c") +if(TARGET moonlight-common-c AND TARGET openssl_external) + add_dependencies(moonlight-common-c openssl_external) +endif() +target_link_libraries(enet PUBLIC NXDK::NXDK NXDK::Net NXDK::ws2_32) +target_compile_options(enet PRIVATE -Wno-unused-function -Wno-error=unused-function) +if(TARGET moonlight-common-c) + target_compile_options(moonlight-common-c PRIVATE -Wno-unused-function -Wno-error=unused-function) + target_link_libraries(moonlight-common-c PRIVATE NXDK::ws2_32) +endif() + +add_executable(${CMAKE_PROJECT_NAME} + ${MOONLIGHT_SOURCES} +) +target_include_directories(${CMAKE_PROJECT_NAME} + SYSTEM PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" +) +target_link_libraries(${CMAKE_PROJECT_NAME} + PUBLIC + NXDK::NXDK + NXDK::NXDK_CXX + NXDK::SDL2 + NXDK::SDL2_Image +) +target_compile_options(${CMAKE_PROJECT_NAME} + PRIVATE + ${MOONLIGHT_COMPILE_OPTIONS} + $<$:-std=gnu++17> +) +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE XBOX NXDK) +add_dependencies(${CMAKE_PROJECT_NAME} moonlight-common-c) + +if(BUILD_DOCS) + add_subdirectory(third-party/doxyconfig docs) +endif() + +add_custom_target(cxbe_convert ALL + COMMAND "${NXDK_DIR}/tools/cxbe/cxbe" + -OUT:${XBOX_XBE_DIR}/default.xbe + -TITLE:${XBE_TITLE} + "${CMAKE_CURRENT_BINARY_DIR}/${XBE_TITLE}.exe" + COMMENT "CXBE conversion: EXE -> XBE" + VERBATIM +) +add_dependencies(cxbe_convert ${CMAKE_PROJECT_NAME}) +add_dependencies(cxbe_convert sync_xbe_assets) + +add_custom_target(xbe_iso ALL + COMMAND "${NXDK_DIR}/tools/extract-xiso/build/extract-xiso" + -c "${XBOX_XBE_DIR}" "${XBOX_ISO_NAME}" + COMMENT "CXBE conversion: XBE -> XISO" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + VERBATIM +) +add_dependencies(xbe_iso cxbe_convert) diff --git a/scripts/find-msys2.cmd b/scripts/find-msys2.cmd new file mode 100644 index 0000000..08881e6 --- /dev/null +++ b/scripts/find-msys2.cmd @@ -0,0 +1,71 @@ +@echo off +setlocal + +set "MOONLIGHT_MSYS2_ROOT=" + +call :try_candidate "%MSYS2_ROOT%" +if defined MOONLIGHT_MSYS2_ROOT goto found + +if defined SystemDrive call :try_candidate "%SystemDrive%\msys64" +if defined MOONLIGHT_MSYS2_ROOT goto found + +for %%D in ("C:\msys64" "C:\tools\msys64") do ( + call :try_candidate "%%~fD" + if defined MOONLIGHT_MSYS2_ROOT goto found +) + +for %%T in (msys2_shell.cmd bash.exe mingw32-make.exe clang++.exe clang.exe) do ( + for /f "delims=" %%I in ('where %%T 2^>nul') do ( + call :try_from_tool "%%~fI" + if defined MOONLIGHT_MSYS2_ROOT goto found + ) +) + +echo MSYS2 installation not found. Set MSYS2_ROOT or add MSYS2 tools to PATH.>&2 +exit /b 1 + +:found +set "MOONLIGHT_MSYS2_SHELL=%MOONLIGHT_MSYS2_ROOT%\msys2_shell.cmd" +set "MOONLIGHT_MSYS2_USR_BIN=%MOONLIGHT_MSYS2_ROOT%\usr\bin" +set "MOONLIGHT_MSYS2_MINGW64_BIN=%MOONLIGHT_MSYS2_ROOT%\mingw64\bin" +set "MOONLIGHT_MSYS2_BASH=%MOONLIGHT_MSYS2_USR_BIN%\bash.exe" + +if not exist "%MOONLIGHT_MSYS2_SHELL%" ( + echo MSYS2 shell not found at %MOONLIGHT_MSYS2_SHELL%>&2 + exit /b 1 +) +if not exist "%MOONLIGHT_MSYS2_BASH%" ( + echo MSYS2 bash not found at %MOONLIGHT_MSYS2_BASH%>&2 + exit /b 1 +) + +endlocal & ( + set "MOONLIGHT_MSYS2_ROOT=%MOONLIGHT_MSYS2_ROOT%" + set "MOONLIGHT_MSYS2_SHELL=%MOONLIGHT_MSYS2_SHELL%" + set "MOONLIGHT_MSYS2_USR_BIN=%MOONLIGHT_MSYS2_USR_BIN%" + set "MOONLIGHT_MSYS2_MINGW64_BIN=%MOONLIGHT_MSYS2_MINGW64_BIN%" + set "MOONLIGHT_MSYS2_BASH=%MOONLIGHT_MSYS2_BASH%" +) +exit /b 0 + +:try_candidate +set "CANDIDATE=%~1" +if not defined CANDIDATE exit /b 0 +for %%R in ("%CANDIDATE%") do set "CANDIDATE=%%~fR" +if exist "%CANDIDATE%\msys2_shell.cmd" set "MOONLIGHT_MSYS2_ROOT=%CANDIDATE%" +exit /b 0 + +:try_from_tool +set "TOOL_PATH=%~1" +if not defined TOOL_PATH exit /b 0 + +if /i "%~nx1"=="msys2_shell.cmd" ( + for %%R in ("%~dp1.") do call :try_candidate "%%~fR" + exit /b 0 +) + +for %%R in ("%~dp1..\..") do call :try_candidate "%%~fR" +if defined MOONLIGHT_MSYS2_ROOT exit /b 0 + +for %%R in ("%~dp1..") do call :try_candidate "%%~fR" +exit /b 0 diff --git a/scripts/find-msys2.sh b/scripts/find-msys2.sh new file mode 100644 index 0000000..65ee141 --- /dev/null +++ b/scripts/find-msys2.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +set -euo pipefail + +try_root() { + local candidate_root="$1" + + if [[ -z "$candidate_root" ]]; then + return 1 + fi + + if [[ -f "$candidate_root/msys2_shell.cmd" ]]; then + printf '%s\n' "$candidate_root" + return 0 + fi + + return 1 +} + +try_from_tool() { + local tool_path="$1" + local tool_dir + + if [[ -z "$tool_path" ]]; then + return 1 + fi + + tool_dir="$(cd "$(dirname "$tool_path")" && pwd)" + + if [[ "$(basename "$tool_path")" == "msys2_shell.cmd" ]]; then + try_root "$tool_dir" && return 0 + fi + + try_root "$(cd "$tool_dir/../.." 2>/dev/null && pwd)" && return 0 + try_root "$(cd "$tool_dir/.." 2>/dev/null && pwd)" && return 0 + return 1 +} + +if try_root "${MSYS2_ROOT:-}"; then + exit 0 +fi + +if [[ -n "${SYSTEMDRIVE:-}" ]]; then + system_drive_letter="$(printf '%s' "$SYSTEMDRIVE" | cut -c1 | tr '[:upper:]' '[:lower:]')" + if try_root "/${system_drive_letter}/msys64"; then + exit 0 + fi +fi + +for candidate_root in /c/msys64 /c/tools/msys64; do + if try_root "$candidate_root"; then + exit 0 + fi +done + +for tool_name in msys2_shell.cmd bash.exe mingw32-make.exe clang++.exe clang.exe; do + if tool_path="$(command -v "$tool_name" 2>/dev/null)" && try_from_tool "$tool_path"; then + exit 0 + fi +done + +echo 'MSYS2 installation not found. Set MSYS2_ROOT or add MSYS2 tools to PATH.' >&2 +exit 1 diff --git a/scripts/run-xemu.cmd b/scripts/run-xemu.cmd index 9f275b7..3ee1e5e 100644 --- a/scripts/run-xemu.cmd +++ b/scripts/run-xemu.cmd @@ -2,16 +2,46 @@ setlocal set "SCRIPT_DIR=%~dp0" -set "MSYS2_SHELL=C:\msys64\msys2_shell.cmd" +for %%I in ("%SCRIPT_DIR%..") do set "PROJECT_ROOT=%%~fI" +set "CALLER_DIR=%CD%" -if not exist "%MSYS2_SHELL%" ( - echo MSYS2 shell not found at %MSYS2_SHELL%>&2 +call :has_explicit_target %* +if errorlevel 1 if not defined MOONLIGHT_XEMU_BUILD_DIR if not defined MOONLIGHT_XEMU_ISO_PATH if not defined MOONLIGHT_XEMU_TARGET_PATH ( + if /I not "%CALLER_DIR%"=="%PROJECT_ROOT%" ( + set "MOONLIGHT_XEMU_TARGET_PATH=%CALLER_DIR%" + ) +) + +call "%SCRIPT_DIR%find-msys2.cmd" +if errorlevel 1 ( exit /b 1 ) -pushd "%SCRIPT_DIR%.." >nul -call "%MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "./scripts/run-xemu.sh %*" +pushd "%PROJECT_ROOT%" >nul +call "%MOONLIGHT_MSYS2_BASH%" -lc "cd \"$1\" && shift && ./scripts/run-xemu.sh \"$@\"" bash "%CD%" %* set "EXIT_CODE=%ERRORLEVEL%" popd >nul exit /b %EXIT_CODE% + +:has_explicit_target +if "%~1"=="" exit /b 1 +if /I "%~1"=="--check" ( + shift + goto has_explicit_target +) +if /I "%~1"=="-h" exit /b 1 +if /I "%~1"=="--help" exit /b 1 +if /I "%~1"=="--build-dir" exit /b 0 +if /I "%~1"=="--iso" exit /b 0 +if /I "%~1"=="--" ( + shift + if "%~1"=="" exit /b 1 + exit /b 0 +) +set "ARG=%~1" +if "%ARG:~0,1%"=="-" ( + shift + goto has_explicit_target +) +exit /b 0 diff --git a/scripts/run-xemu.sh b/scripts/run-xemu.sh index 29a76f8..a8a72e3 100644 --- a/scripts/run-xemu.sh +++ b/scripts/run-xemu.sh @@ -1,10 +1,18 @@ #!/usr/bin/env bash +# Launch xemu against an explicit ISO path or a CMake build directory that +# contains the generated Xbox ISO. + set -euo pipefail usage() { cat <<'EOF' -Usage: run-xemu.sh [--check] [--iso path] +Usage: run-xemu.sh [--check] [--build-dir dir] [--iso path] [path] + +Environment overrides: + MOONLIGHT_XEMU_BUILD_DIR + MOONLIGHT_XEMU_ISO_PATH + MOONLIGHT_XEMU_TARGET_PATH EOF return 0 } @@ -16,6 +24,16 @@ is_windows() { esac } +is_unresolved_ide_macro() { + local value="$1" + + if [[ "$value" =~ ^\$[A-Za-z_][A-Za-z0-9_]*\$$ ]]; then + return 0 + fi + + return 1 +} + to_native_path() { local path="$1" @@ -71,38 +89,192 @@ require_file() { fi } +normalize_cli_path() { + local path="$1" + + if [[ -z "$path" ]]; then + printf '\n' + return 0 + fi + + if is_unresolved_ide_macro "$path"; then + printf '\n' + return 0 + fi + + if is_windows && [[ "$path" =~ ^[A-Za-z]:\\ ]]; then + cygpath -u "$path" + return 0 + fi + + printf '%s\n' "${path//\\//}" + return 0 +} + +default_iso_path() { + local project_root="$1" + local build_dir="${2:-}" + + if [[ -n "$build_dir" ]]; then + local build_dir_iso="$build_dir/xbox/Moonlight.iso" + if [[ -f "$build_dir/Moonlight.iso" ]]; then + printf '%s\n' "$build_dir/Moonlight.iso" + else + printf '%s\n' "$build_dir_iso" + fi + return 0 + fi + + local preferred_path="$project_root/cmake-build-release/xbox/Moonlight.iso" + local candidate + local newest_candidate="" + + shopt -s nullglob + for candidate in \ + "$project_root"/cmake-build-*/xbox/Moonlight.iso \ + "$project_root"/cmake-build-*/Moonlight.iso; do + if [[ -z "$newest_candidate" || "$candidate" -nt "$newest_candidate" ]]; then + newest_candidate="$candidate" + fi + done + shopt -u nullglob + + if [[ -n "$newest_candidate" ]]; then + printf '%s\n' "$newest_candidate" + return 0 + fi + + printf '%s\n' "$preferred_path" + return 0 +} + +resolve_build_dir() { + local path="$1" + + path="$(normalize_cli_path "$path")" + + if [[ -z "$path" ]]; then + printf '\n' + return 0 + fi + + case "$path" in + /*) + printf '%s\n' "$path" + ;; + [A-Za-z]:/*) + printf '%s\n' "$path" + ;; + *) + printf '%s\n' "$project_root/$path" + ;; + esac + + return 0 +} + +resolve_input_path() { + local input_path="$1" + + resolve_build_dir "$input_path" + return 0 +} + +apply_target_path() { + local target_path="$1" + local resolved_path + + resolved_path="$(resolve_input_path "$target_path")" + + if [[ -z "$resolved_path" ]]; then + return 0 + fi + + if [[ -d "$resolved_path" ]]; then + build_dir="$resolved_path" + iso_path="$(default_iso_path "$project_root" "$build_dir")" + else + iso_path="$resolved_path" + fi + + return 0 +} + project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" setup_script="$project_root/scripts/setup-xemu.sh" manifest_sh="$project_root/.local/xemu/paths.sh" -iso_path="$project_root/Moonlight.iso" +build_dir="" +iso_path="$(default_iso_path "$project_root")" check_only=0 +target_path="" + +if [[ -n "${MOONLIGHT_XEMU_BUILD_DIR:-}" ]]; then + build_dir="$(resolve_build_dir "$MOONLIGHT_XEMU_BUILD_DIR")" + iso_path="$(default_iso_path "$project_root" "$build_dir")" +fi + +if [[ -n "${MOONLIGHT_XEMU_ISO_PATH:-}" ]]; then + iso_path="$(resolve_input_path "$MOONLIGHT_XEMU_ISO_PATH")" +fi + +if [[ -n "${MOONLIGHT_XEMU_TARGET_PATH:-}" ]]; then + target_path="$MOONLIGHT_XEMU_TARGET_PATH" +fi while [[ $# -gt 0 ]]; do case "$1" in --check) check_only=1 ;; + --build-dir) + shift + if [[ $# -eq 0 ]]; then + echo 'Missing value for --build-dir' >&2 + exit 2 + fi + build_dir="$(resolve_build_dir "$1")" + iso_path="$(default_iso_path "$project_root" "$build_dir")" + ;; --iso) shift if [[ $# -eq 0 ]]; then echo 'Missing value for --iso' >&2 exit 2 fi - iso_path="$1" + iso_path="$(resolve_input_path "$1")" + ;; + --) + shift + if [[ $# -gt 0 ]]; then + target_path="$1" + shift + fi + if [[ $# -gt 0 ]]; then + echo 'Only one positional path is supported' >&2 + exit 2 + fi + break ;; -h|--help) usage exit 0 ;; *) - echo "Unknown argument: $1" >&2 - usage >&2 - exit 2 + if [[ -z "$target_path" ]]; then + target_path="$1" + else + echo 'Only one positional path is supported' >&2 + exit 2 + fi ;; esac shift done +if [[ -n "$target_path" ]]; then + apply_target_path "$target_path" +fi + if [[ ! -f "$manifest_sh" ]]; then "$setup_script" fi @@ -152,6 +324,9 @@ require_file 'xemu hard disk image' "$hdd_path" write_xemu_config "$xemu_config_path" "$games_dir" "$bootrom_path" "$flashrom_path" "$eeprom_path" "$hdd_path" if [[ "$check_only" -eq 1 ]]; then + if [[ -n "$build_dir" ]]; then + printf 'BUILD_DIR=%s\n' "$build_dir" + fi printf 'XEMU_EXE=%s\n' "$xemu_exe" printf 'XEMU_CONFIG_PATH=%s\n' "$xemu_config_path" printf 'ISO_PATH=%s\n' "$iso_path" diff --git a/scripts/setup-xemu.cmd b/scripts/setup-xemu.cmd index 4f5f7f6..7b1455f 100644 --- a/scripts/setup-xemu.cmd +++ b/scripts/setup-xemu.cmd @@ -2,15 +2,13 @@ setlocal set "SCRIPT_DIR=%~dp0" -set "MSYS2_SHELL=C:\msys64\msys2_shell.cmd" - -if not exist "%MSYS2_SHELL%" ( - echo MSYS2 shell not found at %MSYS2_SHELL%>&2 +call "%SCRIPT_DIR%find-msys2.cmd" +if errorlevel 1 ( exit /b 1 ) pushd "%SCRIPT_DIR%.." >nul -call "%MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "./scripts/setup-xemu.sh %*" +call "%MOONLIGHT_MSYS2_SHELL%" -defterm -here -no-start -mingw64 -c "./scripts/setup-xemu.sh %*" set "EXIT_CODE=%ERRORLEVEL%" popd >nul diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3dc7639..6edfc89 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,15 @@ +# Build the host-native googletest target that exercises platform-neutral +# Moonlight code without pulling the Xbox-only entrypoints into the test binary. + cmake_minimum_required(VERSION 3.13) # https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md#foundational-c-support project(test_moonlight LANGUAGES CXX) +if(NOT DEFINED MOONLIGHT_HOST_TESTABLE_SOURCES) + message(FATAL_ERROR "tests/CMakeLists.txt must be configured from the top-level CMake project") +endif() + set(GTEST_SOURCE_DIR "${CMAKE_SOURCE_DIR}/third-party/googletest") set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE) @@ -10,7 +17,8 @@ add_subdirectory("${GTEST_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/googletest" set(TEST_COVERAGE_COMPILE_OPTIONS) set(TEST_COVERAGE_LINK_OPTIONS) -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT WIN32)) list(APPEND TEST_COVERAGE_COMPILE_OPTIONS -fprofile-arcs -ftest-coverage -ggdb -O0) list(APPEND TEST_COVERAGE_LINK_OPTIONS -fprofile-arcs -ftest-coverage) endif() @@ -23,17 +31,9 @@ file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/*.h ${CMAKE_SOURCE_DIR}/tests/*.cpp) -set(MOONLIGHT_TEST_EXCLUDED_SOURCES - ${CMAKE_SOURCE_DIR}/src/main.cpp - ${CMAKE_SOURCE_DIR}/src/splash/splash_screen.cpp - ${CMAKE_SOURCE_DIR}/src/startup/memory_stats.cpp) - -set(MOONLIGHT_TESTABLE_SOURCES ${MOONLIGHT_SOURCES}) -list(REMOVE_ITEM MOONLIGHT_TESTABLE_SOURCES ${MOONLIGHT_TEST_EXCLUDED_SOURCES}) - add_executable(${PROJECT_NAME} ${TEST_SOURCES} - ${MOONLIGHT_TESTABLE_SOURCES}) + ${MOONLIGHT_HOST_TESTABLE_SOURCES}) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) target_include_directories(${PROJECT_NAME} @@ -55,4 +55,17 @@ target_link_options(${PROJECT_NAME} add_dependencies(${PROJECT_NAME} gtest gtest_main gmock gmock_main) include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME}) +set(TEST_RUNTIME_PROPERTIES) +if(WIN32) + set(_test_runtime_path "${MOONLIGHT_HOST_TOOL_DIR}") + if(_test_runtime_path STREQUAL "") + get_filename_component(_test_runtime_path "${CMAKE_CXX_COMPILER}" DIRECTORY) + endif() + + list(APPEND TEST_RUNTIME_PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${_test_runtime_path}") +endif() + +gtest_discover_tests(${PROJECT_NAME} + DISCOVERY_MODE PRE_TEST + PROPERTIES ${TEST_RUNTIME_PROPERTIES})