diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6682a09b89..8674c22fa1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "ghcr.io/devcontainers/features/python:1": {}, "ghcr.io/devcontainers/features/sshd:1": {} }, - "postCreateCommand": "sudo sh -ce 'apt update && apt install g++-14 -y && echo export CC=gcc-14 CXX=g++-14 >> /etc/profile.d/set-compiler.sh'", + "postCreateCommand": "sudo sh -ce 'wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 20'", "customizations": { "vscode": { "extensions": ["llvm-vs-code-extensions.vscode-clangd", "DavidAnson.vscode-markdownlint"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f29c4be90..9d455273d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,12 +225,6 @@ jobs: git submodule update --init --depth 1 3rdparty/EmulatorExtras git submodule update --init --depth 1 src/maa-cli - - name: Install cross compile toolchains - if: ${{ matrix.arch != 'x86_64' }} - run: | - sudo apt-get update - sudo apt-get install gcc-14-aarch64-linux-gnu g++-14-aarch64-linux-gnu - # Caching not necessary on ubuntu runner - name: Bootstrap MaaDeps env: @@ -238,6 +232,16 @@ jobs: run: | python3 tools/maadeps-download.py ${{ matrix.arch == 'x86_64' && 'x64' || 'arm64' }}-linux + - name: Install cross compile toolchains + run: | + python3 tools/linux-toolchain-download.py ${{ matrix.arch == 'x86_64' && 'x64' || 'arm64' }} + + - name: Install llvm 20 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 20 + - name: Config cmake run: | mkdir -p build @@ -246,10 +250,8 @@ jobs: -DMAADEPS_TRIPLET='maa-${{ matrix.arch == 'x86_64' && 'x64' || 'arm64' }}-linux' \ -DINSTALL_RESOURCE=ON \ -DINSTALL_PYTHON=ON \ - -DMAA_VERSION='${{ needs.meta.outputs.tag }}' - env: - CC: ${{ matrix.arch == 'x86_64' && 'gcc-14' || 'aarch64-linux-gnu-gcc-14' }} - CXX: ${{ matrix.arch == 'x86_64' && 'g++-14' || 'aarch64-linux-gnu-g++-14' }} + -DMAA_VERSION='${{ needs.meta.outputs.tag }}' \ + -DCMAKE_TOOLCHAIN_FILE=cmake/linux/maa-${{ matrix.arch == 'x86_64' && 'x64' || 'arm64' }}-linux-toolchain.cmake - name: Build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index e61a7b5831..6c4a6a53aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,9 @@ if (BUILD_DEBUG_DEMO) target_compile_options(debug_demo PRIVATE "-Wno-error") endif() target_link_libraries(debug_demo MaaCore) + if(LINUX) + target_link_libraries(debug_demo pthread) + endif() add_dependencies(debug_demo MaaCore) # install(TARGETS debug_demo ${MaaCore_install_flatten_args}) diff --git a/cmake/config.cmake b/cmake/config.cmake index b38f64e30f..96191c0bb5 100644 --- a/cmake/config.cmake +++ b/cmake/config.cmake @@ -36,23 +36,43 @@ else() endif() if(LINUX) - execute_process( - COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libstdc++.so.6 - OUTPUT_VARIABLE LIBSTDCPP_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - file(READ_SYMLINK "${LIBSTDCPP_PATH}" LINK_TARGET) - if(NOT IS_ABSOLUTE "${LINK_TARGET}") - get_filename_component(LIBSTDCPP_PATH_DIR "${LIBSTDCPP_PATH}" DIRECTORY) - file(REAL_PATH "${LIBSTDCPP_PATH_DIR}/${LINK_TARGET}" LIBSTDCPP_PATH_REAL) - else() - set(LIBSTDCPP_PATH_REAL "${LINK_TARGET}") - endif() - message(STATUS "libstdc++.so.6 path: ${LIBSTDCPP_PATH_REAL}") - if(NOT EXISTS "${LIBSTDCPP_PATH_REAL}") - message(FATAL_ERROR "File not found: ${LIBSTDCPP_PATH_REAL}") - endif() - install(FILES "${LIBSTDCPP_PATH_REAL}" DESTINATION bin RENAME libstdc++.so.6) + function(copy_and_add_rpath_library LIBNAME) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=${LIBNAME}.so.1 -target ${CMAKE_CXX_COMPILER_TARGET} --sysroot=${CMAKE_SYSROOT} + OUTPUT_VARIABLE LIB_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if("${LIB_PATH}" STREQUAL "${LIBNAME}.so.1}") + message(FATAL_ERROR "Could not locate ${LIBNAME}.so.1 using compiler") + endif() + + file(READ_SYMLINK "${LIB_PATH}" LINK_TARGET) + if(NOT LINK_TARGET) + set(LIB_PATH_REAL "${LIB_PATH}") + elseif(NOT IS_ABSOLUTE "${LINK_TARGET}") + get_filename_component(LIB_PATH_DIR "${LIB_PATH}" DIRECTORY) + file(REAL_PATH "${LIB_PATH_DIR}/${LINK_TARGET}" LIB_PATH_REAL) + else() + set(LIB_PATH_REAL "${LINK_TARGET}") + endif() + + if(NOT EXISTS "${LIB_PATH_REAL}") + message(FATAL_ERROR "File not found: ${LIB_PATH_REAL}") + endif() + + message(STATUS "${LIBNAME}.so.1 path: ${LIB_PATH_REAL}") + + install(FILES "${LIB_PATH_REAL}" DESTINATION . RENAME "${LIBNAME}.so.1") + + get_filename_component(LIB_PATH_DIR "${LIB_PATH_REAL}" DIRECTORY) + list(APPEND CMAKE_BUILD_RPATH "${LIB_PATH_DIR}") + set(CMAKE_BUILD_RPATH "${CMAKE_BUILD_RPATH}" PARENT_SCOPE) + endfunction() + + copy_and_add_rpath_library(libc++) + copy_and_add_rpath_library(libc++abi) + copy_and_add_rpath_library(libunwind) endif() set(CMAKE_CXX_STANDARD 20) diff --git a/cmake/linux/maa-arm64-linux-toolchain.cmake b/cmake/linux/maa-arm64-linux-toolchain.cmake new file mode 100644 index 0000000000..5c545c3f94 --- /dev/null +++ b/cmake/linux/maa-arm64-linux-toolchain.cmake @@ -0,0 +1,19 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +set(XTOOLS_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../MaaDeps/x-tools/aarch64-linux-gnu) +set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu) +set(CMAKE_C_COMPILER clang-20) +set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu) +set(CMAKE_CXX_COMPILER clang++-20) +set(CMAKE_ASM_COMPILER_TARGET aarch64-linux-gnu) +set(CMAKE_ASM_COMPILER clang-20) +set(CMAKE_SYSROOT ${XTOOLS_ROOT}/aarch64-linux-gnu/sysroot) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT}") + +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_LINKER_TYPE LLD) diff --git a/cmake/linux/maa-x64-linux-toolchain.cmake b/cmake/linux/maa-x64-linux-toolchain.cmake new file mode 100644 index 0000000000..9ee9f825e1 --- /dev/null +++ b/cmake/linux/maa-x64-linux-toolchain.cmake @@ -0,0 +1,19 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(XTOOLS_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../MaaDeps/x-tools/x86_64-linux-gnu) +set(CMAKE_C_COMPILER_TARGET x86_64-linux-gnu) +set(CMAKE_C_COMPILER clang-20) +set(CMAKE_CXX_COMPILER_TARGET x86_64-linux-gnu) +set(CMAKE_CXX_COMPILER clang++-20) +set(CMAKE_ASM_COMPILER_TARGET x86_64-linux-gnu) +set(CMAKE_ASM_COMPILER clang-20) +set(CMAKE_SYSROOT ${XTOOLS_ROOT}/x86_64-linux-gnu/sysroot) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT}") + +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC -stdlib=libc++ --sysroot=${CMAKE_SYSROOT} -fuse-ld=lld --gcc-toolchain=${XTOOLS_ROOT}") +set(CMAKE_LINKER_TYPE LLD) diff --git a/docs/zh-cn/develop/linux-tutorial.md b/docs/zh-cn/develop/linux-tutorial.md index 62f17e52b7..ec38b640d7 100644 --- a/docs/zh-cn/develop/linux-tutorial.md +++ b/docs/zh-cn/develop/linux-tutorial.md @@ -22,7 +22,10 @@ Mac 可以使用 `tools/build_macos_universal.zsh` 脚本进行编译。建议 - Ubuntu/Debian ```bash - sudo apt install gcc-14 g++-14 cmake zlib1g-dev + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 20 + sudo apt install cmake ``` 2. 构建第三方库 @@ -32,10 +35,12 @@ Mac 可以使用 `tools/build_macos_universal.zsh` 脚本进行编译。建议 - 下载预构建的第三方库 (推荐的) > **Note** - > 包含在相对较新的 Linux 发行版 (Ubuntu 22.04) 中编译的动态库, 如果您系统中的 libstdc++ 版本较老, 可能遇到 ABI 不兼容的问题. + > ~~包含在相对较新的 Linux 发行版 (Ubuntu 22.04) 中编译的动态库, 如果您系统中的 libstdc++ 版本较老, 可能遇到 ABI 不兼容的问题~~. + > 目前已经基于交叉编译降低了运行环境, 仅需要依赖 glibc 2.31 (ubuntu 20.04). ```bash python tools/maadeps-download.py + python tools/linux-toolchain-download.py ``` 如果您发现上面的方法下载的库由于 ABI 版本等原因无法在您的系统上运行且不希望使用容器等方案, 也可以尝试从头编译 @@ -43,22 +48,28 @@ Mac 可以使用 `tools/build_macos_universal.zsh` 脚本进行编译。建议 - 自行构建第三方库 (将花费较长时间) ```bash - git submodule update --init --recursive + git clone https://github.com/MaaAssistantArknights/MaaDeps cd MaaDeps + # 如果系统环境过低, 请考虑不使用交叉编译, 而是直接使用本地编译环境. + # 需要调整 MaaDeps/vcpkg-overlay/triplet 中的 toolchain 配置. + python linux-toolchain-download.py python build.py ``` 3. 编译 MAA ```bash - CC=gcc-14 CXX=g++-14 cmake -B build \ + cmake -B build \ -DINSTALL_RESOURCE=ON \ - -DINSTALL_PYTHON=ON + -DINSTALL_PYTHON=ON \ + -DCMAKE_TOOLCHAIN_FILE=cmake/linux/maa-x64-linux-toolchain.cmake cmake --build build ``` 来将 MAA 安装到目标位置, 注意 MAA 推荐通过指定 `LD_LIBRARY_PATH` 来运行, 不要使用管理员权限将 MAA 装入 `/usr` + > 现在应该不需要指定LD_LIBRARY_PATH即可运行, 有问题提issue吧 + ```bash cmake --install build --prefix ``` diff --git a/src/MaaCore/Utils/Platform.hpp b/src/MaaCore/Utils/Platform.hpp index 61bba60177..e1db93f28e 100644 --- a/src/MaaCore/Utils/Platform.hpp +++ b/src/MaaCore/Utils/Platform.hpp @@ -23,7 +23,7 @@ using platform::call_command; namespace path_literals { -inline std::filesystem::path operator"" _p(const char* utf8_str, size_t len) +inline std::filesystem::path operator""_p(const char* utf8_str, size_t len) { // 日后再优化( return asst::utils::path(std::string(std::string_view(utf8_str, len))); diff --git a/tools/linux-toolchain-download.py b/tools/linux-toolchain-download.py new file mode 100755 index 0000000000..002155af94 --- /dev/null +++ b/tools/linux-toolchain-download.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +import os +import sys +import urllib.request +import urllib.error +import json +import time +from pathlib import Path +import shutil + +basedir = Path(__file__).parent +maadeps_dir = Path(basedir, "..", "MaaDeps") +download_dir = Path(maadeps_dir, "tarball") + +def detect_host_arch(): + import platform + machine = platform.machine().lower() + system = platform.system().lower() + if machine in {"amd64", "x86_64"}: + machine = "x64" + elif machine in {"x86", "i386", "i486", "i586", "i686"}: + machine = "x86" + elif machine in {"armv7l", "armv7a", "arm", "arm32"}: + machine = "arm" + elif machine in {"arm64", "armv8l", "aarch64"}: + machine = "arm64" + else: + raise Exception("unsupported architecture: " + machine) + if system in {"windows", "linux"}: + pass + elif 'mingw' in system or 'cygwin' in system: + system = "windows" + elif system == "darwin": + system = "osx" + else: + raise Exception("unsupported system: " + system) + if system != "linux": + raise Exception("must on linux") + return machine + +def format_size(num, suffix="B"): + for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: + if abs(num) < 1024.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1024.0 + return f"{num:.1f}Yi{suffix}" + + +class ProgressHook: + def __init__(self): + self.downloaded = 0 + self.last_print = 0 + + def __call__(self, block, chunk, total): + self.downloaded += chunk + t = time.monotonic() + if t - self.last_print >= 0.5 or self.downloaded == total: + self.last_print = t + if total > 0: + print(f"\r [{self.downloaded / total * 100.0:3.1f}%] {format_size(self.downloaded)} / {format_size(total)} \r", end='') + if self.downloaded == total: + print("") + + +def sanitize_filename(filename: str): + import platform + system = platform.system() + if system == "Windows": + filename = filename.translate(str.maketrans("/\\:\"?*|\0", "________")).rstrip('.') + elif system == "Darwin": + filename = filename.translate(str.maketrans("/:\0", "___")) + else: + filename = filename.translate(str.maketrans("/\0", "__")) + return filename + + +def retry_urlopen(*args, **kwargs): + import time + import http.client + for _ in range(5): + try: + resp: http.client.HTTPResponse = urllib.request.urlopen(*args, **kwargs) + return resp + except urllib.error.HTTPError as e: + if e.status == 403 and e.headers.get("x-ratelimit-remaining") == "0": + # rate limit + t0 = time.time() + reset_time = t0 + 10 + try: + reset_time = int(e.headers.get("x-ratelimit-reset", 0)) + except ValueError: + pass + reset_time = max(reset_time, t0 + 10) + print(f"rate limit exceeded, retrying after {reset_time - t0:.1f} seconds") + time.sleep(reset_time - t0) + continue + raise + + +def main(): + if len(sys.argv) == 2: + target_arch = sys.argv[1] + else: + target_arch = detect_host_arch() + print("about to download prebuilt dependency libraries for", target_arch) + # if len(sys.argv) == 1: + # print(f"to specify another triplet, run `{sys.argv[0]} `") + # print(f"e.g. `{sys.argv[0]} x64-windows`") + req = urllib.request.Request("https://api.github.com/repos/MaaXYZ/MaaLinuxToolchain/releases/latest") + token = os.environ.get("GH_TOKEN", os.environ.get("GITHUB_TOKEN", None)) + if token: + req.add_header("Authorization", f"Bearer {token}") + resp = retry_urlopen(req).read() + release = json.loads(resp) + + def split_asset_name(name: str): + if not name.endswith('.tar.xz'): + return None + name = name.replace('.tar.xz', '') + *remainder, architecture = name.rsplit('-', 1) + print(f'{name.ljust(29)} arch:{architecture}') + if architecture in { 'x64', 'arm64' }: + return architecture + return None + toolchain_asset = None + for asset in release["assets"]: + arch = split_asset_name(asset["name"]) + if arch == target_arch: + toolchain_asset = asset + if toolchain_asset: + print("found assets:") + print(" " + toolchain_asset["name"]) + download_dir.mkdir(parents=True, exist_ok=True) + for asset in [toolchain_asset]: + url = asset['browser_download_url'] + print("downloading from", url) + local_file = download_dir / sanitize_filename(asset["name"]) + urllib.request.urlretrieve(url, local_file, reporthook=ProgressHook()) + print("extracting", asset["name"]) + os.system(f'rm -rf {maadeps_dir}/x-tools') + shutil.unpack_archive(local_file, maadeps_dir) + os.system(f'chmod -R +w {maadeps_dir}/x-tools') + else: + raise Exception(f"no binary release found for {target_arch}") + +if __name__ == "__main__": + main()