From ddefd144098d9d7c6d14fc397f8050e1268a1af7 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 6 Oct 2026 10:51:52 +0800 Subject: [PATCH] [fix] repair desktop CI and upgrade outdated Xmake --- .github/actions/ensure-xmake/action.yml | 29 ++++ .github/actions/ensure-xmake/check-version.sh | 98 +++++++++++ .github/scripts/test_xmake_version_check.py | 157 ++++++++++++++++++ .github/workflows/build.yml | 54 +++--- apps/desktop/tests/windows_autostart_test.cpp | 2 +- docker/linux-build/README.md | 11 +- 6 files changed, 329 insertions(+), 22 deletions(-) create mode 100644 .github/actions/ensure-xmake/action.yml create mode 100644 .github/actions/ensure-xmake/check-version.sh create mode 100644 .github/scripts/test_xmake_version_check.py diff --git a/.github/actions/ensure-xmake/action.yml b/.github/actions/ensure-xmake/action.yml new file mode 100644 index 00000000..60bbcfa9 --- /dev/null +++ b/.github/actions/ensure-xmake/action.yml @@ -0,0 +1,29 @@ +name: Ensure minimum Xmake version +description: Keep a compatible Xmake installation, or install the minimum required version. + +inputs: + minimum-version: + description: Minimum supported Xmake version (major.minor.patch). + required: true + +runs: + using: composite + steps: + - name: Check installed Xmake version + id: check + shell: bash + env: + XMAKE_MIN_VERSION: ${{ inputs.minimum-version }} + run: bash "$GITHUB_ACTION_PATH/check-version.sh" + + - name: Install or upgrade Xmake + if: steps.check.outputs.needs-upgrade == 'true' + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: ${{ inputs.minimum-version }} + + - name: Verify Xmake meets the minimum version + shell: bash + env: + XMAKE_MIN_VERSION: ${{ inputs.minimum-version }} + run: bash "$GITHUB_ACTION_PATH/check-version.sh" --verify diff --git a/.github/actions/ensure-xmake/check-version.sh b/.github/actions/ensure-xmake/check-version.sh new file mode 100644 index 00000000..c960b7ba --- /dev/null +++ b/.github/actions/ensure-xmake/check-version.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail +export LC_ALL=C + +verify=false +if [[ $# -eq 1 && "$1" == "--verify" ]]; then + verify=true +elif [[ $# -ne 0 ]]; then + echo "Usage: $0 [--verify]" >&2 + exit 2 +fi + +minimum_version="${XMAKE_MIN_VERSION:-}" +if [[ ! "$minimum_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "XMAKE_MIN_VERSION must be a numeric major.minor.patch version, for example 3.1.1." >&2 + exit 2 +fi + +version_at_least() { + local -a installed_parts required_parts + local index installed_component required_component + IFS=. read -r -a installed_parts <<< "$1" + IFS=. read -r -a required_parts <<< "$2" + + for index in 0 1 2; do + installed_component="${installed_parts[$index]}" + required_component="${required_parts[$index]}" + # Strip leading zeroes and compare lengths first, avoiding both octal + # interpretation and integer overflow in Bash's arithmetic expansion. + while [[ ${#installed_component} -gt 1 && "$installed_component" == 0* ]]; do + installed_component="${installed_component#0}" + done + while [[ ${#required_component} -gt 1 && "$required_component" == 0* ]]; do + required_component="${required_component#0}" + done + if [[ ${#installed_component} -gt ${#required_component} ]]; then + return 0 + elif [[ ${#installed_component} -lt ${#required_component} ]]; then + return 1 + elif [[ "$installed_component" > "$required_component" ]]; then + return 0 + elif [[ "$installed_component" < "$required_component" ]]; then + return 1 + fi + done + return 0 +} + +detected_version="" +needs_upgrade=true +reason="xmake was not found on PATH" +if command -v xmake >/dev/null 2>&1; then + if version_output="$(xmake --root --version 2>&1)"; then + # Xmake can color individual words and version components. Strip ANSI + # CSI sequences before matching, using only Bash 3.2 builtins. + ansi_pattern=$'\033''\[[0-?]*[ -/]*[@-~]' + while [[ "$version_output" =~ $ansi_pattern ]]; do + version_output="${version_output//"${BASH_REMATCH[0]}"/}" + done + version_pattern='(^|[[:space:]])xmake[[:space:]]+v([0-9]+\.[0-9]+\.[0-9]+)([+][[:alnum:].-]+)?([,[:space:]]|$)' + while IFS= read -r line; do + if [[ "$line" =~ $version_pattern ]]; then + detected_version="${BASH_REMATCH[2]}" + break + fi + done <<< "$version_output" + + if [[ -z "$detected_version" ]]; then + reason="xmake --root --version did not report a recognizable Xmake version" + elif version_at_least "$detected_version" "$minimum_version"; then + needs_upgrade=false + else + reason="installed Xmake $detected_version is below $minimum_version" + fi + else + reason="xmake --root --version failed with exit code $?" + fi +fi + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + { + printf 'needs-upgrade=%s\n' "$needs_upgrade" + printf 'detected-version=%s\n' "$detected_version" + } >> "$GITHUB_OUTPUT" +else + printf 'needs-upgrade=%s\n' "$needs_upgrade" + printf 'detected-version=%s\n' "$detected_version" +fi + +if [[ "$needs_upgrade" == true ]]; then + if [[ "$verify" == true ]]; then + echo "Xmake verification failed: $reason. Ensure Xmake $minimum_version or newer is installed and on PATH." >&2 + exit 1 + fi + echo "Xmake upgrade required: $reason. Install Xmake $minimum_version or newer." +else + echo "Keeping installed Xmake $detected_version; it meets the minimum version $minimum_version." +fi diff --git a/.github/scripts/test_xmake_version_check.py b/.github/scripts/test_xmake_version_check.py new file mode 100644 index 00000000..580780cf --- /dev/null +++ b/.github/scripts/test_xmake_version_check.py @@ -0,0 +1,157 @@ +"""Exercise CI Xmake selection with isolated executable and Actions-output mocks.""" + +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +SCRIPT = Path(__file__).resolve().parents[1] / "actions/ensure-xmake/check-version.sh" +BASH = shutil.which("bash") + + +@unittest.skipUnless(BASH, "Bash is required to exercise the CI version check") +class XmakeVersionCheckTest(unittest.TestCase): + def check(self, version_output=None, *, minimum="3.1.1", status=0, arguments=()): + with tempfile.TemporaryDirectory(prefix="crossdesk-xmake-check-") as directory: + root = Path(directory) + output_file = root / "github-output" + output_file.write_text("existing-output=preserved\n", encoding="utf-8") + arguments_file = root / "arguments" + if version_output is not None: + executable = root / "xmake" + executable.write_text( + "#!/bin/sh\n" + 'printf "%s\\n" "$@" > "$XMAKE_TEST_ARGUMENTS"\n' + 'printf "%s" "$XMAKE_TEST_OUTPUT"\n' + 'exit "$XMAKE_TEST_STATUS"\n', + encoding="utf-8", + ) + executable.chmod(0o755) + environment = { + **os.environ, + # The checker only needs shell builtins. Isolating PATH also + # guarantees that missing-tool tests cannot find a real xmake. + "PATH": str(root), + "GITHUB_OUTPUT": str(output_file), + "XMAKE_TEST_ARGUMENTS": str(arguments_file), + "XMAKE_TEST_OUTPUT": version_output or "", + "XMAKE_TEST_STATUS": str(status), + } + if minimum is None: + environment.pop("XMAKE_MIN_VERSION", None) + else: + environment["XMAKE_MIN_VERSION"] = minimum + result = subprocess.run( + [BASH, str(SCRIPT), *arguments], + env=environment, + capture_output=True, + text=True, + check=False, + ) + outputs = dict( + line.split("=", 1) + for line in output_file.read_text(encoding="utf-8").splitlines() + ) + invocation = ( + arguments_file.read_text(encoding="utf-8").splitlines() + if arguments_file.exists() else None + ) + self.assertEqual(outputs["existing-output"], "preserved") + return result, outputs, invocation + + def test_keeps_minimum_and_newer_versions(self): + for version in ("3.1.1", "3.1.2", "3.1.10", "3.2.0", "3.10.0", "4.0.0"): + with self.subTest(version=version): + result, outputs, invocation = self.check(f"xmake v{version}\n") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], "false") + self.assertEqual(outputs["detected-version"], version) + self.assertEqual(invocation, ["--root", "--version"]) + self.assertIn("Keeping installed Xmake", result.stdout) + + def test_older_versions_require_upgrade(self): + for version in ("3.1.0", "3.0.99", "2.99.99"): + with self.subTest(version=version): + result, outputs, _ = self.check(f"xmake v{version}\n") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], "true") + self.assertIn("Install Xmake 3.1.1 or newer", result.stdout) + + def test_components_are_compared_numerically(self): + for installed, minimum, expected in ( + ("3.1.10", "3.1.9", "false"), + ("3.1.9", "3.1.10", "true"), + ("3.2.0", "3.1.99", "false"), + ("3.1.99", "3.2.0", "true"), + ("3.01.009", "3.1.9", "false"), + ("3.1.999999999999999999999", "3.1.9", "false"), + ): + with self.subTest(installed=installed, minimum=minimum): + result, outputs, _ = self.check(f"xmake v{installed}", minimum=minimum) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], expected) + + def test_parses_ansi_colors_build_suffix_and_windows_line_endings(self): + result, outputs, _ = self.check( + "Xmake banner\r\n" + "\x1b[1;38;5;10mxmake\x1b[0m v\x1b[1m3.1.10\x1b[0m+HEAD.abcdef, A build utility\r\n" + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], "false") + self.assertEqual(outputs["detected-version"], "3.1.10") + + def test_missing_broken_and_unrecognizable_tools_require_upgrade(self): + cases = ( + (None, 0), + ("xmake v9.9.9\n", 17), + ("", 0), + ("xmake version unknown\n", 0), + ("xmake v3.1\n", 0), + ("xmake v3.1.1invalid\n", 0), + ("xmake v3.1.1-rc.1\n", 0), + ("otherxmake v9.9.9\n", 0), + ) + for output, status in cases: + with self.subTest(output=output, status=status): + result, outputs, _ = self.check(output, status=status) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], "true") + self.assertEqual(outputs["detected-version"], "") + self.assertIn("Install Xmake 3.1.1 or newer", result.stdout) + + def test_verification_fails_when_install_did_not_provide_required_version(self): + for output, status in ((None, 0), ("xmake v3.1.0", 0), ("unknown", 0), ("xmake v3.1.1", 5)): + with self.subTest(output=output, status=status): + result, outputs, _ = self.check(output, status=status, arguments=("--verify",)) + self.assertEqual(result.returncode, 1) + self.assertEqual(outputs["needs-upgrade"], "true") + self.assertIn("Xmake verification failed", result.stderr) + self.assertIn("3.1.1 or newer", result.stderr) + + def test_verification_accepts_newer_version(self): + result, outputs, _ = self.check("xmake v4.0.0", arguments=("--verify",)) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(outputs["needs-upgrade"], "false") + + def test_invalid_minimum_fails_before_running_xmake(self): + for minimum in (None, "", "3.1", "v3.1.1", "3.1.1+HEAD", "3.1.1\n", "3.a.1"): + with self.subTest(minimum=minimum): + result, outputs, invocation = self.check("xmake v3.1.1", minimum=minimum) + self.assertEqual(result.returncode, 2) + self.assertNotIn("needs-upgrade", outputs) + self.assertIsNone(invocation) + self.assertIn("XMAKE_MIN_VERSION", result.stderr) + + def test_unknown_arguments_fail_before_running_xmake(self): + result, outputs, invocation = self.check("xmake v3.1.1", arguments=("--install",)) + self.assertEqual(result.returncode, 2) + self.assertNotIn("needs-upgrade", outputs) + self.assertIsNone(invocation) + self.assertIn("Usage:", result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 850b4efe..a227414e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ permissions: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + XMAKE_MIN_VERSION: "3.1.1" jobs: build-linux: @@ -37,12 +38,13 @@ jobs: include: - arch: amd64 runner: ubuntu-24.04 - image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.1.1-rust1.92.0 + # Keep using the published dependency image; upgrade Xmake if needed. + image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0 package_script: ./apps/desktop/scripts/linux/pkg_amd64.sh - arch: arm64 runner: ubuntu-24.04-arm - image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.1.1-rust1.92.0 + image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0 package_script: ./apps/desktop/scripts/linux/pkg_arm64.sh container: @@ -108,8 +110,16 @@ jobs: with: submodules: recursive + - name: Ensure minimum Xmake version + uses: ./.github/actions/ensure-xmake + with: + minimum-version: ${{ env.XMAKE_MIN_VERSION }} + - name: Verify Linux build image - run: verify-crossdesk-build-image + shell: bash + env: + EXPECTED_ARCH: ${{ matrix.arch }} + run: bash docker/linux-build/verify-build-image.sh - name: Ensure Wayland build dependencies shell: bash @@ -264,18 +274,20 @@ jobs: restore-keys: | ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}- - - name: Install xmake - run: brew install xmake - - - name: Update xmake repositories - run: xmake repo -u - - name: Checkout code uses: actions/checkout@v5 - name: Initialize submodules run: git submodule update --init --recursive + - name: Ensure minimum Xmake version + uses: ./.github/actions/ensure-xmake + with: + minimum-version: ${{ env.XMAKE_MIN_VERSION }} + + - name: Update xmake repositories + run: xmake repo -u + - name: Build CrossDesk run: | xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=false -vyD @@ -384,13 +396,23 @@ jobs: restore-keys: | ${{ runner.os }}-xmake-deps-intel- - - name: Install xmake + - name: Checkout code + uses: actions/checkout@v5 + + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Ensure minimum Xmake version + uses: ./.github/actions/ensure-xmake + with: + minimum-version: ${{ env.XMAKE_MIN_VERSION }} + + - name: Prepare CUDA dependencies + working-directory: ${{ runner.temp }} run: | - Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content - echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append xmake repo -u xmake create cuda - Set-Location cuda + Push-Location cuda xmake g --theme=plain $cudaPath = "" $packagesPath = "D:\xmake_global\.xmake\packages" @@ -429,12 +451,6 @@ jobs: Write-Host "Copying $source to $target" Copy-Item $source $target -Force - - name: Checkout code - uses: actions/checkout@v5 - - - name: Initialize submodules - run: git submodule update --init --recursive - - name: Copy nsProcess plugin to NSIS folder run: | $nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode" diff --git a/apps/desktop/tests/windows_autostart_test.cpp b/apps/desktop/tests/windows_autostart_test.cpp index a552bb7b..2fe5a5c4 100644 --- a/apps/desktop/tests/windows_autostart_test.cpp +++ b/apps/desktop/tests/windows_autostart_test.cpp @@ -79,7 +79,7 @@ bool CheckTask(ITaskFolder* folder, BSTR task_name, if (FAILED(task->get_Definition(definition.GetAddressOf()))) return false; ComPtr principal; if (FAILED(definition->get_Principal(principal.GetAddressOf()))) return false; - TASK_RUNLEVEL level = TASK_RUNLEVEL_LUA; + TASK_RUNLEVEL_TYPE level = TASK_RUNLEVEL_LUA; TASK_LOGON_TYPE logon = TASK_LOGON_NONE; bool ok = Expect(SUCCEEDED(principal->get_RunLevel(&level)) && level == TASK_RUNLEVEL_HIGHEST, diff --git a/docker/linux-build/README.md b/docker/linux-build/README.md index 1608b395..ab72a6a4 100644 --- a/docker/linux-build/README.md +++ b/docker/linux-build/README.md @@ -8,6 +8,13 @@ PipeWire 0.3/SPA 头文件 SDK)以及根据项目 Xmake 最低支持 3.1.1,与移动端一致,构建环境校验允许更新版本。发布镜像的工具链 tag 记录实际安装的版本;默认使用最低支持版本验证构建。 +常规应用构建目前复用已发布的 `xmake3.0.9` 工具链镜像,并在容器启动后检查 +Xmake 版本:缺失或低于 3.1.1 时安装或升级,达标时保留现有版本,随后再次校验。 +Linux、macOS 和 Windows 桌面构建共用 `.github/actions/ensure-xmake`;Linux 还会 +执行仓库中的 `verify-build-image.sh` 校验环境。这样既保留预编译 +依赖缓存,也不依赖尚未发布的 `xmake3.1.1` 镜像。切换镜像 tag 前必须确认 +amd64 和 arm64 变体均已发布。 + ## 发布方式 推送影响镜像的文件到 `ci/linux-build-image` 分支时, @@ -47,5 +54,5 @@ Ubuntu 20.04 仓库只提供 PipeWire 0.2。镜像通过 ## 更新依赖 修改 Dockerfile、xmake 清单、本地包配方或子模块中的包配方后,推送该分支即可。 -升级工具链时应同时修改 Dockerfile 的版本参数、工作流中的 `STACK_TAG`,以及 -常规构建工作流引用的镜像 tag。 +升级工具链时应先修改 Dockerfile 的版本参数和镜像工作流中的 `STACK_TAG`, +等两个架构的镜像发布成功后,再修改常规构建工作流引用的镜像 tag。