diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e77c7004..35dd95f91 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,4 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +# Per-platform matrix: install Qt, configure + build, test, package, upload. name: CI Build on: @@ -10,7 +9,7 @@ on: jobs: formatting-check: - name: formatting-check + name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -22,91 +21,54 @@ jobs: fallback-style: LLVM build-windows: + name: Windows x86_64 needs: formatting-check runs-on: windows-latest steps: - uses: actions/checkout@master with: submodules: recursive - + - name: Install Qt uses: jurplel/install-qt-action@v4 with: version: 6.5.3 target: desktop arch: win64_mingw + tools: 'tools_mingw1310' cache: true cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install Windows Discord RPC - shell: bash - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc.zip - unzip discord_rpc.zip - cp ./discord-rpc/win64-dynamic/lib/discord-rpc.lib ./lib/ - cp ./discord-rpc/win64-dynamic/bin/discord-rpc.dll ./bin/ - cp ./discord-rpc/win64-dynamic/include/discord*.h ./lib/ - - - name: Install Windows BASS + - name: Configure shell: bash - run: | - curl http://www.un4seen.com/files/bass24.zip -o bass.zip - unzip -d bass -o bass.zip - cp ./bass/c/bass.h ./lib - cp ./bass/c/x64/bass.lib ./lib/ - cp ./bass/x64/bass.dll ./bin/ - - curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip - unzip -d bass -o bassopus.zip - cp ./bass/c/bassopus.h ./lib - cp ./bass/c/x64/bassopus.lib ./lib/ - cp ./bass/x64/bassopus.dll ./bin/ - - - name: Clone Apng plugin - uses: actions/checkout@master - with: - repository: jurplel/QtApng - path: ./qtapng - - - name: Build Apng plugin - run: | - cd ./qtapng - cmake . -G "MinGW Makefiles" - cmake --build . --config Release - mkdir -p ${{ github.workspace }}/bin/imageformats/ - cp plugins/imageformats/qapng.dll ${{ github.workspace }}/bin/imageformats/qapng.dll + env: + BUILD_CONFIG: Release + run: ./configure.sh - name: Build - run: | - cmake . -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Release - cmake --build . --config Release - - - name: Deploy Windows - working-directory: ${{github.workspace}}/bin/ shell: bash run: | - windeployqt --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw ./Attorney_Online.exe + source build.env + "$NINJA" - - name: Clone Themes - uses: actions/checkout@master - with: - repository: AttorneyOnline/AO2-Themes - path: "bin/base/themes" + - name: Run tests + shell: bash + run: ctest --output-on-failure - - name: Cleanup Themes Checkout - run: | - rm ./bin/base/themes/.gitignore - rm ./bin/base/themes/.gitattributes - Remove-Item -Path "./bin/base/themes/.git" -Recurse -Force + - name: Package + shell: bash + run: ./scripts/package-windows.sh - name: Upload Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Windows - path: ${{github.workspace}}/bin - + name: AttorneyOnline-Windows + path: dist/AttorneyOnline-windows-*.zip + archive: false + build-linux: + name: Linux x86_64 needs: formatting-check runs-on: ubuntu-22.04 steps: @@ -126,122 +88,134 @@ jobs: cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install Linux Discord RPC - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc.zip - unzip discord_rpc.zip - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./lib/ - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./bin/ - cp ./discord-rpc/linux-dynamic/include/discord*.h ./src/ + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh - - name: Install Linux BASS + - name: Build run: | - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - unzip -d bass -o bass.zip - cp ./bass/c/bass.h ./lib - cp ./bass/libs/x86_64/libbass.so ./lib/ - cp ./bass/libs/x86_64/libbass.so ./bin/ - - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - unzip -d bass -o bassopus.zip - cp ./bass/c/bassopus.h ./lib - cp ./bass/libs/x86_64/libbassopus.so ./lib/ - cp ./bass/libs/x86_64/libbassopus.so ./bin/ - - - name: Clone Apng plugin - uses: actions/checkout@master + source build.env + "$NINJA" + + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure + + - name: Package + run: ./scripts/package-linux.sh + + - name: Upload Dynamic Artifact + uses: actions/upload-artifact@v7 with: - repository: jurplel/QtApng - path: ./qtapng + name: AttorneyOnline-Linux-Dynamic + path: dist/AttorneyOnline-linuxdynamic-*.zip + archive: false - - name: Build Apng plugin - run: | - cd ./qtapng - cmake . -D CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE="${{ github.workspace }}/bin/imageformats/" - cmake --build . --config Release + - name: Upload AppImage Artifact + uses: actions/upload-artifact@v7 + with: + name: AttorneyOnline-Linux-AppImage + path: dist/AttorneyOnline-linuxappimage-*.zip + archive: false + + build-linux-arm: + name: Linux arm64 + needs: formatting-check + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@master + with: + submodules: recursive + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + aqtversion: '==3.1.*' + # Qt ships Linux ARM64 desktop binaries only from 6.7 onward. + version: '6.8.3' + host: 'linux_arm64' + target: 'desktop' + arch: 'linux_gcc_arm64' + cache: true + cache-key-prefix: install-qt-action + modules: 'qtimageformats qtwebsockets' - # install plugin - cp plugins/imageformats/libqapng.so ${QT_ROOT_DIR}/plugins/imageformats + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh - name: Build run: | - cmake . - cmake --build . --config Release + source build.env + "$NINJA" + + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure - - name: Clone Themes - uses: actions/checkout@master + - name: Package + run: ./scripts/package-linux.sh + + - name: Upload Dynamic Artifact + uses: actions/upload-artifact@v7 with: - repository: AttorneyOnline/AO2-Themes - path: "bin/base/themes" + name: AttorneyOnline-Linux-arm64-Dynamic + path: dist/AttorneyOnline-linuxdynamic-*.zip + archive: false - - name: Cleanup Themes Checkout - run: | - rm ./bin/base/themes/.gitignore - rm ./bin/base/themes/.gitattributes - rm -r ./bin/base/themes/.git + - name: Upload AppImage Artifact + uses: actions/upload-artifact@v7 + with: + name: AttorneyOnline-Linux-arm64-AppImage + path: dist/AttorneyOnline-linuxappimage-*.zip + archive: false - - name: Deploy Linux - shell: bash - run: | - cd ${{ github.workspace }}/bin - mkdir ./imageformats - cp ../qtapng/plugins/imageformats/libqapng.so ./imageformats - cp ../data/logo-client.png ./icon.png - cp ../README_LINUX.md . - cp ../scripts/DYNAMIC_INSTALL.sh ./INSTALL.sh - chmod +x INSTALL.sh - chmod +x Attorney_Online + build-macos: + name: macOS arm64 + needs: formatting-check + runs-on: macos-14 + steps: + - uses: actions/checkout@master + with: + submodules: recursive - patchelf --add-rpath . Attorney_Online + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + aqtversion: '==3.1.*' + version: '6.5.3' + host: 'mac' + target: 'desktop' + arch: 'clang_64' + cache: true + cache-key-prefix: install-qt-action + modules: 'qtimageformats qtwebsockets' - cd .. - tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh - - name: Create AppImage - shell: bash - run: | - # necessary, apparently - sudo apt install libxcb-cursor0 - # from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md - wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) - mv appimagetool-*-x86_64.AppImage appimagetool - chmod +x appimagetool - - mkdir -p AppDir/usr/bin - mkdir -p AppDir/usr/lib/plugins/imageformats - mkdir -p AppDir/usr/share/applications - - cp bin/Attorney_Online AppDir/usr/bin - cp bin/lib*.so AppDir/usr/lib - cp scripts/Attorney_Online.desktop AppDir/usr/share/applications - cp data/logo-client.png AppDir/Attorney_Online.png - - GIT_SHORT_SHA="${GITHUB_SHA::8}" - QTDIR=${QT_ROOT_DIR} ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop - ARCH=x86_64 VERSION=${GIT_SHORT_SHA} ./appimagetool AppDir - - - name: Deploy AppImage - shell: bash + - name: Build run: | - mkdir bin-appimage - cp -r bin/base bin-appimage - cp data/logo-client.png bin-appimage/icon.png - cp README_LINUX.md bin-appimage - cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh - cp Attorney_Online-*-x86_64.AppImage bin-appimage - chmod +x bin-appimage/INSTALL.sh - chmod +x bin-appimage/Attorney_Online-*-x86_64.AppImage + source build.env + "$NINJA" - tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure - - name: Upload Dynamic Artifact - uses: actions/upload-artifact@master - with: - name: Attorney_Online-Linux-Dynamic - path: Attorney_Online-Dynamic.tar + - name: Package + run: ./scripts/package-macos.sh - - name: Upload AppImage Artifact - uses: actions/upload-artifact@master + - name: Upload Artifact + uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Linux-AppImage - path: Attorney_Online-AppImage.tar + name: AttorneyOnline-macOS + path: dist/AttorneyOnline-macos-*.zip + archive: false diff --git a/.gitignore b/.gitignore index acb71559c..cc9ef9351 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ base/serverlist.txt Makefile* object_script* /android/gradle* -/Attorney_Online_*.rc +/AttorneyOnline_*.rc /attorney_online_*_plugin_import.cpp server/__pycache__ discord/ @@ -52,11 +52,12 @@ cmake-build-* .ninja_deps .ninja_log .qt/ -Attorney_Online_autogen/ +AttorneyOnline_autogen/ CMakeCache.txt CMakeFiles/ Testing/ build.ninja +CTestTestfile.cmake cmake_install.cmake test/CMakeFiles/ test/CTestTestfile.cmake @@ -64,3 +65,4 @@ test/cmake_install.cmake test/test_aopacket_autogen/ test/test_aopacket cmake_cmd.txt +build.env diff --git a/CMakeLists.txt b/CMakeLists.txt index ebaaae253..3e39f8c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ option(AO_ENABLE_DISCORD_RPC "Enable Discord Rich Presence" ON) find_package(QT NAMES Qt6) find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets Concurrent WebSockets UiTools) -qt_add_executable(Attorney_Online +qt_add_executable(AttorneyOnline src/aoapplication.cpp src/aoapplication.h src/aoblipplayer.cpp @@ -105,19 +105,25 @@ qt_add_executable(Attorney_Online src/network/serverinfo.h src/network/serverinfo.cpp ) -set_target_properties(Attorney_Online PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") +set_target_properties(AttorneyOnline PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") if(WIN32) + set_target_properties(AttorneyOnline PROPERTIES OUTPUT_NAME "Attorney Online") if(CMAKE_BUILD_TYPE STREQUAL "Release") - set_property(TARGET Attorney_Online PROPERTY WIN32_EXECUTABLE true) + set_property(TARGET AttorneyOnline PROPERTY WIN32_EXECUTABLE true) set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/data/logo-client.rc") - target_sources(Attorney_Online PRIVATE ${APP_ICON_RESOURCE_WINDOWS}) + target_sources(AttorneyOnline PRIVATE ${APP_ICON_RESOURCE_WINDOWS}) endif() endif() -target_include_directories(Attorney_Online PRIVATE src lib) -target_link_directories(Attorney_Online PRIVATE lib) -target_link_libraries(Attorney_Online PRIVATE +# Linux avoids spaces in filenames; the executable ships as AttorneyOnline.bin. +if(UNIX AND NOT APPLE) + set_target_properties(AttorneyOnline PROPERTIES SUFFIX ".bin") +endif() + +target_include_directories(AttorneyOnline PRIVATE src lib) +target_link_directories(AttorneyOnline PRIVATE lib) +target_link_libraries(AttorneyOnline PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network @@ -130,14 +136,49 @@ target_link_libraries(Attorney_Online PRIVATE ) if(AO_ENABLE_DISCORD_RPC) - target_compile_definitions(Attorney_Online PRIVATE AO_ENABLE_DISCORD_RPC) - target_link_libraries(Attorney_Online PRIVATE discord-rpc) + target_compile_definitions(AttorneyOnline PRIVATE AO_ENABLE_DISCORD_RPC) + target_link_libraries(AttorneyOnline PRIVATE discord-rpc) endif() if(AO_BUILD_TESTS) + enable_testing() add_subdirectory(test) endif() -set_target_properties(Attorney_Online PROPERTIES +set_target_properties(AttorneyOnline PROPERTIES LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin> RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin>) + +if(APPLE) + set(MACOSX_BUNDLE_ICON "${CMAKE_CURRENT_LIST_DIR}/data/logo-client.icns") + target_sources(AttorneyOnline PRIVATE ${MACOSX_BUNDLE_ICON}) + set_source_files_properties(${MACOSX_BUNDLE_ICON} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + + set_target_properties(AttorneyOnline PROPERTIES + OUTPUT_NAME "Attorney Online" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME "Attorney Online" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.attorneyonline.AttorneyOnline" + MACOSX_BUNDLE_BUNDLE_VERSION "2" + MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0" + MACOSX_BUNDLE_ICON_FILE "logo-client.icns") + + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib") + add_custom_command(TARGET AttorneyOnline POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/Contents/PlugIns/imageformats" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib" + "$/Contents/PlugIns/imageformats/" + VERBATIM) + endif() + + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/base") + add_custom_command(TARGET AttorneyOnline POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/bin/base" + "$/Contents/Resources/base" + VERBATIM) + endif() +endif() diff --git a/README.md b/README.md index 92f91d790..3b1709357 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,24 @@ ### Prerequisites -Unix-like systems will expect a C toolchain, installed using eg.: +A C/C++ toolchain: Ubuntu: `sudo apt install build-essential` macOS: `xcode-select --install` -You may also need openGL libraries to compile QApng, eg: +Qt **6.5 or newer**, installed via the [Qt online installer](https://doc.qt.io/qt-6/qt-online-installation.html). +Check off the following under "Additional Libraries": +- Qt Image Formats +- Qt WebSockets -Ubuntu: `sudo apt install libgl1-mesa-dev` -macOS: `brew install glfw glew` +On Windows, also check off a toolchain (MinGW), CMake, and Ninja under +"Developer and Designer tools". ### Setup -This program's main dependency is Qt and the currently recommended version for development is **6.5.3**. See [this link](https://doc.qt.io/qt-6/qt-online-installation.html) -on how to install Qt. You will need to check off the following under "Additional Libraries": -- Qt Image formats -- Qt WebSockets - -Under "Developer and Designer tools", you may also want to check off: -- CMake -- Ninja -- If you're on Windows, a toolchain (MinGW) - -Assuming all this is in place, you should be able to run `configure.sh` to generate the necessary build files. -This also compiles the program and shows a cmake command that can be used to recreate the build files. +Run `./configure.sh`. It detects Qt, installs any other build tools it needs, +fetches the remaining dependencies, and generates the build files, then prints +the command to compile the program. ### Content @@ -42,28 +36,29 @@ You can get it from https://ao-dl.b-cdn.net/vanilla_full_2024_8_2.zip This should be put in `./bin/base` -### Formatting - -All code should be formatted according to the `.clang-format` file. -This will be checked by CI and will fail if the code is not formatted correctly. +## Formatting -## Running Tests -Running tests requires Catch2 and cmake +All code must be formatted according to the `.clang-format` file. CI runs a +`clang-format` check (version 17) and fails the build if any file under `src/` +is not formatted correctly. Format your changes before pushing: ```sh -mkdir cbuild && cd cbuild -cmake .. -make test +clang-format -i src/ # format a specific file +git clang-format # format only the lines you changed +``` -# usage: run all tests -./test/test +## Running Tests -# usage: Optionally specify tests and success verbosity -./test/test [bass] --success -``` +Tests are written with [Qt Test](https://doc.qt.io/qt-6/qtest-overview.html) and +registered with CTest. After running `./configure.sh` and building, run them from +the repo root: -`[noci]` tag is used to disable a test on GitHub actions +```sh +ctest --output-on-failure +# or run a single test binary directly, e.g. +./test/test_aopacket +``` ## Credits diff --git a/README_LINUX.md b/README_LINUX.md deleted file mode 100644 index 146799b3e..000000000 --- a/README_LINUX.md +++ /dev/null @@ -1,28 +0,0 @@ -## Running on Linux - -There are two download options for running on Linux: the **dynamically-linked** build and the **AppImage**. The dynamic build is lighter, but might only run on newer systems. The AppImage is a bit bigger, but should run seamlessly on most systems (anything newer than Ubuntu 22.04 LTS). - -Each version also accompanies an `INSTALL.sh` script that will create a desktop file for AO pointing to where the script was ran. This will enable you to run AO from an app launcher. Note that moving AO's folder will require running this script again. - -### AppImage - -If you downloaded the **AppImage** version, it should just be plug-and-play. If you run into errors or bugs, contact us in our [Discord server](https://discord.gg/wWvQ3pw) or open an [issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). - -### Dynamic - -If you downloaded the **dynamically-linked** version, use the `launch.sh` script to run AO. - -You may need to install some libraries in your system. These are the commands to run on a terminal for some distributions: - -* Arch Linux: -``` -$ sudo pacman -S qt6-base qt6-tools qt6-websockets qt6-imageformats -``` -* Fedora: -``` -$ sudo dnf install qt6-qtbase qt6-qttools qt6-qtwebsockets qt6-qtimageformats -``` -* Ubuntu 22.04 LTS: -``` -$ sudo apt-get install qt6base-dev libqt6uitools6 libqt6websockets6 qt6-image-formats-plugins -``` diff --git a/cmake/FindWrapOpenGL.cmake b/cmake/FindWrapOpenGL.cmake new file mode 100644 index 000000000..737b0b2ac --- /dev/null +++ b/cmake/FindWrapOpenGL.cmake @@ -0,0 +1,43 @@ +# Override for Qt's FindWrapOpenGL.cmake on macOS. +# +# Qt 6.5–6.8's bundled FindWrapOpenGL.cmake unconditionally adds +# `-framework AGL` to QtGui's public link interface. AGL was removed from +# the macOS SDK around Xcode 16 / macOS 15, so the link fails with +# "framework 'AGL' not found". Fixed upstream in Qt 6.9. +# +# This file is a copy of Qt's module with the single AGL target_link_libraries +# line removed. It is picked up via CMAKE_MODULE_PATH before Qt's copy. +# +# Original: Copyright (C) 2022 The Qt Company Ltd. SPDX BSD-3-Clause. + +if(TARGET WrapOpenGL::WrapOpenGL) + set(WrapOpenGL_FOUND ON) + return() +endif() + +set(WrapOpenGL_FOUND OFF) + +find_package(OpenGL ${WrapOpenGL_FIND_VERSION}) + +if (OpenGL_FOUND) + set(WrapOpenGL_FOUND ON) + + add_library(WrapOpenGL::WrapOpenGL INTERFACE IMPORTED) + if(APPLE) + get_target_property(__opengl_fw_lib_path OpenGL::GL IMPORTED_LOCATION) + if(__opengl_fw_lib_path AND NOT __opengl_fw_lib_path MATCHES "/([^/]+)\\.framework$") + get_filename_component(__opengl_fw_path "${__opengl_fw_lib_path}" DIRECTORY) + endif() + + if(NOT __opengl_fw_path) + set(__opengl_fw_path "-framework OpenGL") + endif() + + target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_fw_path}) + else() + target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE OpenGL::GL) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WrapOpenGL DEFAULT_MSG WrapOpenGL_FOUND) diff --git a/configure.sh b/configure.sh index ebb8b16e4..aab937f30 100755 --- a/configure.sh +++ b/configure.sh @@ -16,22 +16,35 @@ detect_platform() { echo "${platform}" } -# Basic data such as platform can be global +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) echo "x86_64";; + arm64|aarch64) echo "arm64";; + *) echo "unknown";; + esac +} + PLATFORM=$(detect_platform) -BUILD_CONFIG="Debug" -QT_VERSION="6.5.3" +ARCH=$(detect_arch) +QT_MIN_VERSION="6.5.0" + +# BUILD_CONFIG (Debug|Release, default Debug) and QT_ROOT_DIR (the Qt toolchain +# dir; empty auto-detects under ~/Qt) are read from the environment. +BUILD_CONFIG="${BUILD_CONFIG:-Debug}" +QT_ROOT_DIR="${QT_ROOT_DIR:-}" print_help() { - echo "Usage: $0 [options]" - echo "Options:" + echo "Usage: [BUILD_CONFIG=Debug|Release] [QT_ROOT_DIR=path] $0 [command]" + echo "Commands:" echo " -h, --help: Print this help message" - echo " clean: Remove all files from lib, bin and tmp" - echo " QT_ROOT=path: Specify the root path to where Qt is installed (eg. /c/Qt/)" + echo " clean: Remove everything configure.sh writes (deps, build files, build.env, cmake_cmd.txt)" + echo "Environment variables:" + echo " BUILD_CONFIG=Debug|Release: CMake build type (default: Debug)" + echo " QT_ROOT_DIR=path: Qt toolchain dir to use directly (eg. ~/Qt/6.8.3/gcc_arm64); empty auto-detects under ~/Qt" } -# Check if a given command returns a non-zero exit code check_command() { - # Hack to not make the whole script exit.. + # set +e around the probe so a failure returns non-zero instead of exiting. set +e if ! "$@" &> /dev/null; then set -e @@ -42,30 +55,15 @@ check_command() { } find_qt() { + # Emit the Qt root if a common install location exists, else empty string. local qt_root="" - - # Function to check if a dir exists - check_path() { - if [[ -d "$1" ]]; then - qt_root="$1" - return 0 - else - return 1 - fi - } - - # Check common Qt installation paths on different OSes if [[ "$PLATFORM" == "windows" ]]; then - # Windows paths, maybe check for more in the future - check_path "/c/Qt" - elif [[ "$PLATFORM" == "linux" ]]; then - check_path "$HOME/Qt" - elif [[ "$PLATFORM" == "macos" ]]; then - check_path "$HOME/Qt" + qt_root="/c/Qt" + else + qt_root="$HOME/Qt" fi - # If qt-cmake is found, print the path - if [[ -n "$qt_root" ]]; then + if [[ -d "$qt_root" ]]; then echo "$qt_root" else echo "" @@ -73,95 +71,122 @@ find_qt() { } find_qtpath() { - local qt_path="" - - check_path() { - if [[ -d "$1" ]]; then - qt_path="$1" - return 0 - else - return 1 - fi - } - + # Emit the newest Qt >= QT_MIN_VERSION under $QT_ROOT that has a usable + # desktop toolchain for this platform/arch, else empty string. + local -a candidates=() if [[ "$PLATFORM" == "windows" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/mingw_64" - elif [[ "$PLATFORM" == "linux" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/gcc_64" + candidates=(mingw_64) elif [[ "$PLATFORM" == "macos" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/macos" + candidates=(macos) + elif [[ "$PLATFORM" == "linux" ]]; then + if [[ "$ARCH" == "arm64" ]]; then + candidates=(gcc_arm64 arm64) + else + candidates=(gcc_64) + fi fi - echo "$qt_path" + local best_ver="" + local best_path="" + + shopt -s nullglob + local dir ver tc sub path + for dir in "$QT_ROOT"/*/ ; do + ver=$(basename "$dir") + [[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue + + # Prefer a known toolchain dir name; otherwise fall back to any subdir + # that provides the Qt6 CMake package (what we pass as CMAKE_PREFIX_PATH) + # and isn't a cross-compile target (android/wasm/ios). + path="" + for tc in "${candidates[@]}"; do + [[ -d "${dir}${tc}" ]] && { path="${dir}${tc}"; break; } + done + if [[ -z "$path" ]]; then + for sub in "${dir}"*/ ; do + case "$(basename "$sub")" in android*|wasm*|ios*) continue ;; esac + if [[ -d "${sub}lib/cmake/Qt6" ]]; then + path="${sub%/}"; break + fi + done + fi + [[ -n "$path" ]] || continue + + if [[ "$(printf '%s\n%s\n' "$QT_MIN_VERSION" "$ver" | sort -V | head -n 1)" != "$QT_MIN_VERSION" ]]; then + continue + fi + if [[ -z "$best_ver" || "$(printf '%s\n%s\n' "$best_ver" "$ver" | sort -V | tail -n 1)" == "$ver" ]]; then + best_ver="$ver" + best_path="$path" + fi + done + shopt -u nullglob + + echo "$best_path" } find_cmake() { - local cmake_path="" - - # Function to check if a file exists - check_path() { - if [[ -f "$1" ]]; then - cmake_path="$1" - return 0 - else - return 1 - fi - } + # Prefer cmake on PATH, fall back to the cmake bundled with Qt; empty if neither. + if command -v cmake >/dev/null 2>&1; then + echo "cmake" + return 0 + fi - # See if we can find the cmake bundled with Qt + local cmake_path="" if [[ "$PLATFORM" == "windows" ]]; then - check_path "${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" + cmake_path="${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" elif [[ "$PLATFORM" == "linux" ]]; then - check_path "${QT_ROOT}/Tools/CMake/bin/cmake" + cmake_path="${QT_ROOT}/Tools/CMake/bin/cmake" elif [[ "$PLATFORM" == "macos" ]]; then - check_path "${QT_ROOT}/Tools/CMake/CMake.app/Contents/bin/cmake" - else - echo "Unsupported platform: ${PLATFORM}" - return 1 + cmake_path="${QT_ROOT}/Tools/CMake/CMake.app/Contents/bin/cmake" fi - # If cmake is found, print the path - if [[ -n "$cmake_path" ]]; then + if [[ -f "$cmake_path" ]]; then echo "$cmake_path" - return 0 else echo "" - return 1 fi } find_mingw() { - # Find a mingw installation bundled with Qt - - QT_TOOLS_PATH="${QT_ROOT}/Tools" - - mingw_dir=$(find "${QT_TOOLS_PATH}" -maxdepth 1 -type d -name "mingw*" -print0 | xargs -0 ls -td | head -n 1) - - # Find returns . if the directory is not found - if [[ "$mingw_dir" == "." ]]; then - mingw_dir="" + # Emit the newest MinGW bundled under ${QT_ROOT}/Tools/, else empty. + local tools_path="${QT_ROOT}/Tools" + if [[ ! -d "$tools_path" ]]; then + echo "" + return 0 fi + local mingw_dir="" + mingw_dir=$(find "$tools_path" -maxdepth 1 -type d -name "mingw*" -print0 \ + | xargs -0 -r ls -td 2>/dev/null \ + | head -n 1) + echo "$mingw_dir" } find_ninja() { - # Find a ninja installation bundled with Qt - QT_TOOLS_PATH="${QT_ROOT}/Tools" - - local ninja_path="" + # Prefer ninja on PATH, fall back to the ninja bundled with Qt; empty if neither. + if command -v ninja >/dev/null 2>&1; then + echo "ninja" + return 0 + fi + local bundled="" if [[ "$PLATFORM" == "windows" ]]; then - ninja_path="${QT_TOOLS_PATH}/Ninja/ninja.exe" + bundled="${QT_ROOT}/Tools/Ninja/ninja.exe" else - ninja_path="${QT_TOOLS_PATH}/Ninja/ninja" + bundled="${QT_ROOT}/Tools/Ninja/ninja" + fi + + if [[ -f "$bundled" ]]; then + echo "$bundled" + return 0 fi - echo "$ninja_path" + echo "" } get_zip() { - # Check if at least two arguments are provided if [ "$#" -lt 2 ]; then echo "Usage: get_zip [ ...]" return 1 @@ -169,16 +194,12 @@ get_zip() { mkdir -p ./tmp - # URL of the zip file url="$1" shift zip_filename=$(basename "$url") - - # Temporary file to store the downloaded zip tmp_zip=./tmp/"$zip_filename" - # Download the zip file curl -L "$url" -o "$tmp_zip" if [ $? -ne 0 ]; then echo "Failed to download the zip file from $url" @@ -186,42 +207,38 @@ get_zip() { return 1 fi - # Sanity check: zip file is there if [ ! -f "$tmp_zip" ]; then echo "Error: The zip file '$tmp_zip' does not exist." return 1 fi # First, check that all the specified files exist in the zip archive + zip_listing=$(unzip -l "$tmp_zip") for arg in "$@" ; do src_file="${arg%%:*}" - if ! unzip -l "$tmp_zip" | grep -q "$src_file"; then + if ! grep -q "$src_file" <<< "$zip_listing"; then echo "Error: The file '$src_file' does not exist in the zip archive $tmp_zip." return 1 fi done - # Extract the specified files to their destinations while [ "$#" -gt 0 ]; do src_dst="$1" src_file="${src_dst%%:*}" dst_dir="${src_dst##*:}" - # Create the destination directory if it doesn't exist mkdir -p "$dst_dir" - unzip -j "$tmp_zip" "$src_file" -d "$dst_dir" + unzip -o -j "$tmp_zip" "$src_file" -d "$dst_dir" shift done - # Clean up the temporary zip file rm -rf "$tmp_zip" } get_bass() { echo "Checking for BASS..." - # If lib/bass.h exists, assume that BASS is already present if [ -f "./lib/bass.h" ]; then echo "BASS is installed." return 0 @@ -234,10 +251,12 @@ get_bass() { c/x64/bass.lib:./lib \ x64/bass.dll:./bin elif [[ "$PLATFORM" == "linux" ]]; then + local arch_dir="x86_64" + [[ "$ARCH" == "arm64" ]] && arch_dir="aarch64" get_zip https://www.un4seen.com/files/bass24-linux.zip \ c/bass.h:./lib \ - libs/x86_64/libbass.so:./lib \ - libs/x86_64/libbass.so:./bin + "libs/${arch_dir}/libbass.so:./lib" \ + "libs/${arch_dir}/libbass.so:./bin" elif [[ "$PLATFORM" == "macos" ]]; then get_zip https://www.un4seen.com/files/bass24-osx.zip \ c/bass.h:./lib \ @@ -247,7 +266,6 @@ get_bass() { get_bassopus() { echo "Checking for BASSOPUS..." - # If lib/bassopus.h exists, assume that BASSOPUS is already present if [ -f "./lib/bassopus.h" ]; then echo "BASSOPUS is installed." return 0 @@ -260,10 +278,12 @@ get_bassopus() { c/x64/bassopus.lib:./lib \ x64/bassopus.dll:./bin elif [[ "$PLATFORM" == "linux" ]]; then + local arch_dir="x86_64" + [[ "$ARCH" == "arm64" ]] && arch_dir="aarch64" get_zip https://www.un4seen.com/files/bassopus24-linux.zip \ c/bassopus.h:./lib \ - libs/x86_64/libbassopus.so:./lib \ - libs/x86_64/libbassopus.so:./bin + "libs/${arch_dir}/libbassopus.so:./lib" \ + "libs/${arch_dir}/libbassopus.so:./bin" elif [[ "$PLATFORM" == "macos" ]]; then get_zip https://www.un4seen.com/files/bassopus24-osx.zip \ c/bassopus.h:./lib \ @@ -273,7 +293,6 @@ get_bassopus() { get_discordrpc() { echo "Checking for Discord RPC..." - # If lib/discord_rpc.h exists, assume that Discord RPC is already present if [ -f "./lib/discord_rpc.h" ]; then echo "Discord RPC is installed." return 0 @@ -287,16 +306,26 @@ get_discordrpc() { discord-rpc/win64-dynamic/include/discord_rpc.h:./lib \ discord-rpc/win64-dynamic/include/discord_register.h:./lib elif [[ "$PLATFORM" == "linux" ]]; then - get_zip https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip \ - discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./lib \ - discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./bin \ - discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ - discord-rpc/linux-dynamic/include/discord_register.h:./lib + if [[ "$ARCH" == "x86_64" ]]; then + get_zip https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip \ + discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./lib \ + discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./bin \ + discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ + discord-rpc/linux-dynamic/include/discord_register.h:./lib + else + # discord-rpc v3.4.0 only ships x86_64 binaries, no arm64 + echo "Skipping Discord RPC on Linux ${ARCH} (no native binary available)." + fi elif [[ "$PLATFORM" == "macos" ]]; then - get_zip https://github.com/discord/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip \ - discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib:./lib \ - discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ - discord-rpc/osx-dynamic/include/discord_rpc.h:./lib + if [[ "$ARCH" == "x86_64" ]]; then + get_zip https://github.com/discord/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip \ + discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib:./lib \ + discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ + discord-rpc/osx-dynamic/include/discord_register.h:./lib + else + # discord-rpc v3.4.0 only ships x86_64 binaries, no arm64 + echo "Skipping Discord RPC on macOS ${ARCH} (no native binary available)." + fi fi } @@ -332,7 +361,8 @@ get_qtapng() { $CMAKE . \ -G Ninja \ -DCMAKE_MAKE_PROGRAM="$NINJA" \ - -DCMAKE_PREFIX_PATH="$QT_PATH" \ + -DCMAKE_PREFIX_PATH="$QT_ROOT_DIR" \ + -DCMAKE_MODULE_PATH="${SCRIPT_DIR}/cmake" \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" @@ -362,86 +392,113 @@ get_themes() { rm -rf ./bin/base/themes/.git } +install_build_tools() { + if [[ "$PLATFORM" == "linux" ]]; then + local -a pkgs=() + command -v ninja >/dev/null 2>&1 || pkgs+=(ninja-build) + command -v patchelf >/dev/null 2>&1 || pkgs+=(patchelf) + command -v cmake >/dev/null 2>&1 || pkgs+=(cmake) + command -v curl >/dev/null 2>&1 || pkgs+=(curl) + # GL devlibs satisfy Qt6Gui's WrapOpenGL, libxcb-cursor0 lets Qt apps launch + local libs + for libs in libxcb-cursor0 libgl1-mesa-dev libglvnd-dev mesa-common-dev; do + dpkg -s "$libs" >/dev/null 2>&1 || pkgs+=("$libs") + done + if [ ${#pkgs[@]} -gt 0 ]; then + echo "Installing system packages: ${pkgs[*]}" + sudo apt-get update + sudo apt-get install -y "${pkgs[@]}" + fi + elif [[ "$PLATFORM" == "macos" ]]; then + if ! command -v ninja >/dev/null 2>&1; then + echo "Installing system build tools (ninja)..." + brew install ninja + fi + fi +} + +clean() { + echo "Cleaning up all files written by configure.sh..." + + rm -rf ./lib/* ./bin/* ./tmp/* ./qtapng/ + rm -f ./build.env ./cmake_cmd.txt + rm -rf ./.cmake/ ./.qt/ ./CMakeFiles/ ./AttorneyOnline_autogen/ ./Testing/ + rm -f ./CMakeCache.txt ./cmake_install.cmake ./CTestTestfile.cmake ./build.ninja ./.ninja_deps ./.ninja_log + rm -rf ./test/CMakeFiles/ ./test/test_aopacket_autogen/ + rm -f ./test/CTestTestfile.cmake ./test/cmake_install.cmake ./test/test_aopacket +} + configure() { - # If -h is passed, print help if [ "$#" -gt 0 ] && { [ "$1" = "-h" ] || [ "$1" = "--help" ]; }; then print_help exit 0 fi - # If clean is passed, remove all files from lib, bin and tmp - if [ "$#" -gt 0 ] && { [ "$1" = "clean" ]; }; then - echo "Cleaning up... removing lib, bin and tmp" - rm -rf ./lib/* - rm -rf ./bin/* - rm -rf ./tmp/* - rm -rf ./qtapng/ + if [ "$#" -gt 0 ] && [ "$1" = "clean" ]; then + clean exit 0 fi - echo "Platform: ${PLATFORM}" + echo "Platform: ${PLATFORM} (${ARCH})" + echo "Build config: ${BUILD_CONFIG}" - # If platform is unknown, terminate if [ "$PLATFORM" == "unknown" ]; then echo "Unknown platform. Aborting." exit 1 fi - # Now we look for qt - QT_ROOT="" + if [ "$#" -gt 0 ]; then + echo "Unknown argument: $1" + print_help + exit 1 + fi - # If QT_ROOT=path is passed, use that - if [ "$#" -gt 0 ] && [ "${1%%=*}" = "QT_ROOT" ]; then - QT_ROOT="${1#*=}" - shift - # Try to find it otherwise + install_build_tools + + # find Qt installation if not defined explicitly + if [ -n "$QT_ROOT_DIR" ]; then + if [ ! -d "$QT_ROOT_DIR" ]; then + echo "$QT_ROOT_DIR is not a directory. Aborting." + exit 1 + fi + QT_ROOT="$(cd "$QT_ROOT_DIR/../.." && pwd)" else QT_ROOT=$(find_qt) if [ -z "$QT_ROOT" ]; then echo "Qt not found. Aborting."; exit 1; fi - fi - if [ ! -d "$QT_ROOT" ]; then - echo "$QT_ROOT is not a directory. Aborting." - exit 1 + QT_ROOT_DIR=$(find_qtpath) + if [ -z "$QT_ROOT_DIR" ] || [ ! -d "$QT_ROOT_DIR" ]; then + echo "No Qt >= ${QT_MIN_VERSION} found under ${QT_ROOT}. Aborting." + exit 1 + fi fi echo "Using Qt root: $QT_ROOT" + echo "Using Qt installation: $QT_ROOT_DIR" - QT_PATH=$(find_qtpath) - if [ ! -d "$QT_PATH" ]; then - echo "$QT_PATH is not a directory. Aborting." - exit 1 - fi - echo "Using Qt installation: $QT_PATH" - - # Check for cmake, and prefer the one bundled with Qt CMAKE=$(find_cmake) if [ -z "$CMAKE" ]; then - echo "No cmake bundled with Qt found. Trying path..." - if ! check_command cmake ; then - echo "CMake not found. Aborting." - exit 1 - fi - CMAKE="cmake" + echo "CMake not found (checked PATH and Qt Tools). Aborting." + exit 1 fi check_command "$CMAKE" --version || { echo "cmake not working. Aborting."; exit 1; } echo "Using cmake: $CMAKE" - # Find the compiler bundled in Qt + # Strongly prefer MinGW bundled with Qt on Windows CC="" CXX="" - # If we're on Windows, find mingw if [[ "$PLATFORM" == "windows" ]]; then MINGW_PATH=$(find_mingw) - if [ -z "$MINGW_PATH" ]; then - echo "MinGW not found. Aborting." - exit 1 + if [ -n "$MINGW_PATH" ]; then + CC="${MINGW_PATH}/bin/gcc.exe" + CXX="${MINGW_PATH}/bin/g++.exe" + else + echo "No MinGW bundled with Qt found. Trying PATH..." + CC="gcc" + CXX="g++" fi - CC="${MINGW_PATH}/bin/gcc.exe" - CXX="${MINGW_PATH}/bin/g++.exe" else - # On non-Windows platforms, use the system compiler, as it's usually safe CC="gcc" CXX="g++" fi @@ -456,44 +513,56 @@ configure() { check_command "$NINJA" --version || { echo "Ninja not working. Aborting"; exit 1; } echo "Using Ninja: $NINJA" - # Check basic dependencies check_command curl --help || { echo "Command curl not found. Aborting"; exit 1; } check_command unzip --help || { echo "Command unzip not found. Aborting"; exit 1; } - # Make sure key folders exist mkdir -p ./tmp/ mkdir -p ./lib/ mkdir -p ./bin/ - # Get the dependencies get_bass get_bassopus get_discordrpc get_qtapng get_themes - # Typically, IDEs like running cmake themselves, but we need the binary to fix dependencies correctly + # Discord RPC only ships x86_64 binaries, so turn it off on arm64. + EXTRA_CMAKE_FLAGS="" + if [[ "$ARCH" != "x86_64" ]]; then + EXTRA_CMAKE_FLAGS="-DAO_ENABLE_DISCORD_RPC=OFF" + fi + FULL_CMAKE_CMD="\ $CMAKE . \ -G Ninja \ -DCMAKE_MAKE_PROGRAM=${NINJA} \ --DCMAKE_PREFIX_PATH=${QT_PATH} \ +-DCMAKE_PREFIX_PATH=${QT_ROOT_DIR} \ +-DCMAKE_MODULE_PATH=${SCRIPT_DIR}/cmake \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DCMAKE_C_COMPILER=${CC} \ --DCMAKE_CXX_COMPILER=${CXX}" +-DCMAKE_CXX_COMPILER=${CXX} \ +${EXTRA_CMAKE_FLAGS}" $FULL_CMAKE_CMD - $NINJA - if [[ "$PLATFORM" == "windows" ]]; then - echo "Fixing dependencies..." - windeployqt="${QT_PATH}/bin/windeployqt.exe" - "$windeployqt" --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw ./bin/Attorney_Online.exe - fi + # Record the resolved environment so it can be reused later + { + echo "PLATFORM=\"${PLATFORM}\"" + echo "ARCH=\"${ARCH}\"" + echo "QT_ROOT_DIR=\"${QT_ROOT_DIR}\"" + echo "NINJA=\"${NINJA}\"" + echo "CMAKE=\"${CMAKE}\"" + echo "BUILD_CONFIG=\"${BUILD_CONFIG}\"" + echo "MINGW_PATH=\"${MINGW_PATH:-}\"" + } > build.env - echo "Configuration and build complete." - echo "Full cmake cmd: $FULL_CMAKE_CMD" echo "$FULL_CMAKE_CMD" > cmake_cmd.txt + + echo + echo "Dependencies fetched and build files generated (build type: ${BUILD_CONFIG})." + echo "To compile, run:" + echo + echo "${NINJA}" } configure "$@" diff --git a/scripts/APPIMAGE_INSTALL.sh b/scripts/APPIMAGE_INSTALL.sh deleted file mode 100644 index 8599cd5a5..000000000 --- a/scripts/APPIMAGE_INSTALL.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env sh - -# exit on error -set -e - -# Move to script's directory -SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -cd "${SCRIPT_DIR}" - -#add .desktop file (which should allow most DE's easy access to the program -mkdir -p ~/.local/share/applications - -# desktop files don't like spaces in the Exec field, we have to replace them with "\s" -appimage="$(echo Attorney_Online-*-x86_64.AppImage)" -escaped_exec="$(echo "$(pwd)" | sed 's/ /\\s/g')"/"$appimage" - -desktop_file="\ -[Desktop Entry] -Type=Application -Name=Attorney Online -Comment=The courtroom drama simulator -Path=$(pwd) -Exec=\"$escaped_exec\" -Icon=$(pwd)/icon.png" - -echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop - -#marking the program as executable -chmod +x Attorney_Online-*-x86_64.AppImage - -#running the executable -./Attorney_Online-*-x86_64.AppImage diff --git a/scripts/APPIMAGE_README.md b/scripts/APPIMAGE_README.md new file mode 100644 index 000000000..78de57dc3 --- /dev/null +++ b/scripts/APPIMAGE_README.md @@ -0,0 +1,15 @@ +## Attorney Online for Linux (AppImage) + +This is the **AppImage** build. It bundles Qt and should run on most systems +(anything newer than Ubuntu 22.04 LTS) with no extra setup. + +### Running + +The AppImage is plug-and-play, just run the `.AppImage` file. Or run +`./install.sh` to add Attorney Online to your app launcher; it creates a desktop +entry pointing to this folder. Note that moving this folder requires running +`install.sh` again. + +If you run into errors or bugs, reach us in our +[Discord server](https://discord.gg/wWvQ3pw) or open an +[issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). diff --git a/scripts/Attorney_Online.desktop b/scripts/AttorneyOnline.desktop similarity index 59% rename from scripts/Attorney_Online.desktop rename to scripts/AttorneyOnline.desktop index 16ed60029..880168a4a 100644 --- a/scripts/Attorney_Online.desktop +++ b/scripts/AttorneyOnline.desktop @@ -2,6 +2,7 @@ Type=Application Name=Attorney Online Comment=The courtroom drama simulator -Exec=Attorney_Online -Icon=Attorney_Online +Exec=AttorneyOnline.bin +Icon=AttorneyOnline +StartupWMClass=AttorneyOnline Categories=Game; diff --git a/scripts/DYNAMIC_INSTALL.sh b/scripts/DYNAMIC_INSTALL.sh deleted file mode 100644 index a9fbc2c40..000000000 --- a/scripts/DYNAMIC_INSTALL.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env sh - -# exit on error -set -e - -# Move to script's directory -SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -cd "${SCRIPT_DIR}" - -#add .desktop file (which should allow most DE's easy access to the program -mkdir -p ~/.local/share/applications - -# desktop files don't like spaces in the Exec field, we have to replace them with "\s" -escaped_exec="$(echo "$(pwd)" | sed 's/ /\\s/g')"/Attorney_Online - -desktop_file="\ -[Desktop Entry] -Type=Application -Name=Attorney Online -Comment=The courtroom drama simulator -Path=$(pwd) -Exec=\"$escaped_exec\" -Icon=$(pwd)/icon.png" - -echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop - -#running the program -chmod +x Attorney_Online -./Attorney_Online diff --git a/scripts/DYNAMIC_README.md b/scripts/DYNAMIC_README.md new file mode 100644 index 000000000..faf141edc --- /dev/null +++ b/scripts/DYNAMIC_README.md @@ -0,0 +1,37 @@ +## Attorney Online for Linux (dynamic build) + +This is the **dynamically-linked** build. It's lighter than the AppImage but uses +your system's Qt, so it may only run on newer distributions. + +### Running + +Run the binary directly: + +``` +./AttorneyOnline.bin +``` + +Or run `./install.sh` to add Attorney Online to your app launcher — it creates a +desktop entry pointing to this folder. Note that moving this folder requires +running `install.sh` again. + +### Dependencies + +You may need to install Qt libraries first. Commands for some distributions: + +#### Arch Linux +``` +sudo pacman -S qt6-base qt6-tools qt6-websockets qt6-imageformats +``` +#### Fedora +``` +sudo dnf install qt6-qtbase qt6-qttools qt6-qtwebsockets qt6-qtimageformats +``` +#### Ubuntu 22.04 LTS +``` +sudo apt-get install qt6base-dev libqt6uitools6 libqt6websockets6 qt6-image-formats-plugins +``` + +If you run into errors or bugs, reach us in our +[Discord server](https://discord.gg/wWvQ3pw) or open an +[issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). diff --git a/scripts/appimage_install.sh b/scripts/appimage_install.sh new file mode 100644 index 000000000..e80385e2d --- /dev/null +++ b/scripts/appimage_install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env sh + +set -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +cd "${SCRIPT_DIR}" + +mkdir -p ~/.local/share/applications + +# desktop files don't like spaces in the Exec field, replace with "\s" +appimage="AttorneyOnline.AppImage" +escaped_exec="$(echo "$(pwd)/$appimage" | sed 's/ /\\s/g')" + +desktop_file="\ +[Desktop Entry] +Type=Application +Name=Attorney Online +Comment=The courtroom drama simulator +Path=$(pwd) +Exec=\"$escaped_exec\" +Icon=$(pwd)/icon.png +StartupWMClass=AttorneyOnline" + +echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop + +chmod +x "$appimage" + +echo "Attorney Online has been added to your app menu." +echo "You can also run it directly with: ./$appimage" diff --git a/scripts/dynamic_install.sh b/scripts/dynamic_install.sh new file mode 100644 index 000000000..b88294798 --- /dev/null +++ b/scripts/dynamic_install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env sh + +set -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +cd "${SCRIPT_DIR}" + +mkdir -p ~/.local/share/applications + +# desktop files don't like spaces in the Exec field, replace with "\s" +escaped_exec="$(echo "$(pwd)/AttorneyOnline.bin" | sed 's/ /\\s/g')" + +desktop_file="\ +[Desktop Entry] +Type=Application +Name=Attorney Online +Comment=The courtroom drama simulator +Path=$(pwd) +Exec=\"$escaped_exec\" +Icon=$(pwd)/icon.png +StartupWMClass=AttorneyOnline" + +echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop + +#make the program executable +chmod +x AttorneyOnline.bin + +echo "Attorney Online has been added to your app menu." +echo "You can also run it directly with: ./AttorneyOnline.bin" diff --git a/scripts/macos_release.sh b/scripts/macos_release.sh deleted file mode 100644 index 2ed736492..000000000 --- a/scripts/macos_release.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# This script prepares the compiled bundle for shipping as a standalone release -# Assumes the Qt bin folder is in PATH -# Should be used on a "Release" build from QT creator -# Note that this DOES NOT add the base/ folder - -# Exit on errors and unset variables -set -eu - -ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/" - -cd ${ROOT_DIR} - -# This thing basically does all the work -/usr/local/opt/qt/bin/macdeployqt ../bin/Attorney_Online.app - -# Need to add the dependencies -cp ../lib/* ../bin/Attorney_Online.app/Contents/Frameworks - -# libbass has a funny path for some reason, just use rpath -install_name_tool -change @loader_path/libbass.dylib @rpath/libbass.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online -install_name_tool -change @loader_path/libbassopus.dylib @rpath/libbassopus.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online - -zip -r -9 ../bin/Attorney_Online_macOS.zip ../bin/ \ No newline at end of file diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh new file mode 100755 index 000000000..e75eacf05 --- /dev/null +++ b/scripts/package-linux.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +if [ ! -f "./bin/AttorneyOnline.bin" ]; then + echo "Error: ./bin/AttorneyOnline.bin not found. Build first with the command configure.sh printed." + exit 1 +fi + +mkdir -p "${QT_ROOT_DIR}/plugins/imageformats" +cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformats/" + +### Dynamic build (expects system Qt at runtime) +( + cd ./bin + cp ../data/logo-client.png ./icon.png + cp ../scripts/DYNAMIC_README.md ./README.md + cp ../scripts/dynamic_install.sh ./install.sh + chmod +x install.sh AttorneyOnline.bin + patchelf --add-rpath . AttorneyOnline.bin +) + +### AppImage (self-contained) +case "$(uname -m)" in + aarch64|arm64) APPIMAGE_ARCH=aarch64 ;; + *) APPIMAGE_ARCH=x86_64 ;; +esac + +wget -c "https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-${APPIMAGE_ARCH}.AppImage" | head -n 1 | cut -d '"' -f 2)" +mv appimagetool-*-"${APPIMAGE_ARCH}".AppImage appimagetool +chmod +x appimagetool + +mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications +cp bin/AttorneyOnline.bin AppDir/usr/bin +cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true +cp scripts/AttorneyOnline.desktop AppDir/usr/share/applications +cp data/logo-client.png AppDir/AttorneyOnline.png + +GIT_SHORT_SHA="${GITHUB_SHA:-}" +GIT_SHORT_SHA="${GIT_SHORT_SHA:0:8}" +if [ -z "$GIT_SHORT_SHA" ]; then + GIT_SHORT_SHA="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +fi +QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/AttorneyOnline.desktop + +# go-appimage clobbers permissions, needs fixing +find AppDir -type f -name 'ld-linux-*' -exec chmod +x {} + + +ARCH="${APPIMAGE_ARCH}" VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir + +rm -rf bin-appimage +mkdir bin-appimage +cp -r bin/base bin-appimage +cp data/logo-client.png bin-appimage/icon.png +cp scripts/APPIMAGE_README.md bin-appimage/README.md +cp scripts/appimage_install.sh bin-appimage/install.sh +cp ./*-"${APPIMAGE_ARCH}".AppImage "bin-appimage/AttorneyOnline.AppImage" +chmod +x "bin-appimage/install.sh" "bin-appimage/AttorneyOnline.AppImage" + +DYN_ZIP="AttorneyOnline-${PLATFORM}dynamic-${ARCH}-${GIT_SHORT_SHA}.zip" +APP_ZIP="AttorneyOnline-${PLATFORM}appimage-${ARCH}-${GIT_SHORT_SHA}.zip" +mkdir -p dist +( cd bin && zip -r -y "../dist/${DYN_ZIP}" . -x '.gitignore' ) +( cd bin-appimage && zip -r -y "../dist/${APP_ZIP}" . ) + +echo "Wrote ${ROOT_DIR}/dist/${DYN_ZIP} and ${ROOT_DIR}/dist/${APP_ZIP}" diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh new file mode 100755 index 000000000..600bb39b4 --- /dev/null +++ b/scripts/package-macos.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +APP="./bin/Attorney Online.app" +if [ ! -d "$APP" ]; then + echo "Error: ${APP} not found. Build first with the command configure.sh printed." + exit 1 +fi + +echo "Bundling dependencies into ${APP}..." + +cp ./lib/libbass.dylib ./lib/libbassopus.dylib "${APP}/Contents/MacOS/" +if [ -f ./lib/libdiscord-rpc.dylib ]; then + cp ./lib/libdiscord-rpc.dylib "${APP}/Contents/MacOS/" +fi + +mkdir -p "${APP}/Contents/PlugIns/imageformats" +cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" + +"${QT_ROOT_DIR}/bin/macdeployqt" "$APP" -verbose=1 + +# macdeployqt invalidates signatures, we need to fix that here or else macOS will call the app damaged and refuse to run +# note that it still needs to be manually approved in Privacy & Security but oh well +codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true +codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true +codesign --force --deep --sign - "$APP" + +sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" +[ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" +mkdir -p dist +( cd ./bin && zip -r -y "../dist/${ZIP}" "Attorney Online.app" ) + +echo "Wrote ${ROOT_DIR}/dist/${ZIP}" diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh new file mode 100755 index 000000000..81af52220 --- /dev/null +++ b/scripts/package-windows.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +EXE="./bin/Attorney Online.exe" +if [ ! -f "$EXE" ]; then + echo "Error: ${EXE} not found. Build first with the command configure.sh printed." + exit 1 +fi + +echo "Running windeployqt on ${EXE}..." +"${QT_ROOT_DIR}/bin/windeployqt.exe" \ + --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw "$EXE" + +# needed becauce windeployqt is a bit unreliable with MINGW +if [ -n "${MINGW_PATH:-}" ]; then + for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do + cp "${MINGW_PATH}/bin/${dll}" ./bin/ + done +fi + +sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" +[ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" +mkdir -p dist +pwsh -NoProfile -Command "Compress-Archive -Path (Get-ChildItem -Path bin -Exclude '.gitignore').FullName -DestinationPath 'dist/${ZIP}' -Force" + +echo "Wrote ${ROOT_DIR}/dist/${ZIP}" diff --git a/scripts/wasabi_program.sh b/scripts/wasabi_program.sh index 37feac6eb..60f5b4912 100755 --- a/scripts/wasabi_program.sh +++ b/scripts/wasabi_program.sh @@ -24,11 +24,11 @@ export S3_MANIFESTS="s3://ao-manifests" export S3_ARCHIVES="s3://ao-downloads" export VERSION=$(git describe --tags) -export ARCHIVE="Attorney_Online_${VERSION}_${ARTIFACT_SUFFIX}" +export ARCHIVE="AttorneyOnline_${VERSION}_${ARTIFACT_SUFFIX}" ${S3_COPY} ${S3_MANIFESTS}/${MANIFEST} . node $(dirname $0)/update_manifest.js ${MANIFEST} ${VERSION} \ - -f ${ARCHIVE} -e Attorney_Online.exe + -f ${ARCHIVE} -e "Attorney Online.exe" ${S3_COPY} ${ARCHIVE} ${S3_ARCHIVES} ${S3_COPY} ${MANIFEST} ${S3_MANIFESTS} diff --git a/src/file_functions.cpp b/src/file_functions.cpp index cbdd640d1..74869ecd7 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include bool file_exists(QString file_path) { @@ -77,5 +79,28 @@ QString get_app_path() QString get_base_path() { +#ifdef Q_OS_MAC + return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).absoluteFilePath("base") + "/"; +#else return QDir(get_app_path()).absoluteFilePath("base") + "/"; +#endif +} + +void seed_base_if_missing() +{ +#ifdef Q_OS_MAC + const QString user_base = get_base_path(); + if (dir_exists(user_base)) + { + return; + } + + const QString bundled_base = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/base"); + if (!dir_exists(bundled_base)) + { + return; + } + + QProcess::execute("ditto", {bundled_base, user_base}); +#endif } diff --git a/src/file_functions.h b/src/file_functions.h index 252148fd0..617222518 100644 --- a/src/file_functions.h +++ b/src/file_functions.h @@ -8,3 +8,7 @@ bool exists(QString p_path); QString get_app_path(); QString get_base_path(); + +// macOS only: on first run, seed the writable base (in Application Support) from +// the read-only default base bundled in the .app. No-op on other platforms. +void seed_base_if_missing(); diff --git a/src/lobby.cpp b/src/lobby.cpp index c019135d6..2074dd840 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aoutils.h" #include "demoserver.h" +#include "file_functions.h" #include "gui_utils.h" #include "networkmanager.h" #include "widgets/direct_connect_dialog.h" @@ -305,11 +306,13 @@ void Lobby::on_about_clicked() "respective owners." "

Running on Qt version %2 with the BASS audio engine.
" "APNG plugin loaded: %3" - "

Built on %4") + "

Built on %4" + "

Base folder: %5") .arg(ao_app->get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(hasApng ? tr("Yes") : tr("No")) - .arg(QLatin1String(__DATE__)); + .arg(QLatin1String(__DATE__)) + .arg(get_base_path()); QMessageBox::about(this, tr("About"), msg); } diff --git a/src/main.cpp b/src/main.cpp index 2743eeb24..f54734206 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,12 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); + QApplication::setDesktopFileName("AttorneyOnline"); + + // On macOS, we distribute with a built-in base in the .app but this is not writeable + // so we copy the base folder into the preferred base path (~/Library/Application Support/Attorney Online) + seed_base_if_missing(); + #ifdef ANDROID if (QtAndroid::checkPermission("android.permission.READ_EXTERNAL_STORAGE") == QtAndroid::PermissionResult::Denied) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c78740c1c..c9aecd395 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,8 +4,6 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Test REQUIRED) set(CMAKE_INCLUDE_CURRENT_DIR ON) -enable_testing(true) - set(SKIP_AUTOMOC ON) function(ao_declare_test test_id)