mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 08:42:43 +08:00
[build] unify minimum Xmake version across platforms
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
|
||||
- JDK 17 或更新版本(可使用 Android Studio 自带 JBR)。
|
||||
- Android SDK Platform 36、Build Tools 36.0.0、NDK `28.2.13676358`。
|
||||
- Xmake **3.1.1**、Python 3.9+、Git、CMake 和主机编译工具。
|
||||
- Xmake **3.1.1 或更新版本**、Python 3.9+、Git、CMake 和主机编译工具。
|
||||
- Python 的 setuptools、wheel,用于构建依赖的宿主工具。
|
||||
|
||||
先在仓库根目录初始化子模块:
|
||||
@@ -49,8 +49,7 @@ macOS 命令行示例:
|
||||
```sh
|
||||
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||||
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
||||
# 若 PATH 中的 Xmake 不是 3.1.1,通过 XMAKE_BIN 指定对应可执行文件。
|
||||
# 构建器也识别 ~/.cache/crossdesk/toolchains/xmake-3.1.1/xmake。
|
||||
# 使用 PATH 中的 Xmake(最低 3.1.1),或通过 XMAKE_BIN 指定其他可执行文件。
|
||||
cd apps/android
|
||||
./gradlew :app:assembleDebug
|
||||
```
|
||||
@@ -94,7 +93,7 @@ git push origin android-v1.6.0-20261002
|
||||
|
||||
已有 Release 需要补传服务器或刷新下载信息时,可手动运行 `Update version.json from Release` 并填写已有 `source_tag`,无需重新构建。
|
||||
|
||||
工作流使用 Ubuntu 24.04、JDK 17、Python 3.13、SDK 36、Build Tools 36.0.0、NDK r28c 和 Xmake 3.1.1。Gradle 使用工程内的 Wrapper,原生构建器固定 Xmake 包仓库版本。SDK/NDK 使用独立目录缓存,按宿主系统、架构和工具链版本区分;命中缓存时跳过 SDK 安装,未命中时在安装完成后立即保存,即使后续 APK 构建失败,下次也可复用。Xmake 可执行环境、固定版本的包仓库和 pip 下载也有各自的缓存;Ubuntu 系统工具只安装缺失项。
|
||||
工作流使用 Ubuntu 24.04、JDK 17、Python 3.13、SDK 36、Build Tools 36.0.0、NDK r28c,并使用最低支持的 Xmake 3.1.1 验证构建;本地允许使用更新的 Xmake。Gradle 使用工程内的 Wrapper,原生构建器固定 Xmake 包仓库版本。SDK/NDK 使用独立目录缓存,按宿主系统、架构和工具链版本区分;命中缓存时跳过 SDK 安装,未命中时在安装完成后立即保存,即使后续 APK 构建失败,下次也可复用。Xmake 可执行环境、固定版本的包仓库和 pip 下载也有各自的缓存;Ubuntu 系统工具只安装缺失项。
|
||||
|
||||
Gradle 缓存与 Android 原生依赖缓存分别管理;原生缓存按宿主架构、构建脚本及 MiniRTC 配方区分,不复用桌面或 iOS 的配置和目标文件。GitHub 托管 runner 每次启动仍是新环境;第一次运行或缓存失效时需要安装。不同分支和标签可读取默认分支的缓存,因此建议先在默认分支运行一次 `Build Android` 预热,再创建发布标签。
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
set_project("crossdesk_android")
|
||||
set_xmakever("3.1.1")
|
||||
set_languages("c++17")
|
||||
add_rules("mode.release", "mode.debug")
|
||||
set_policy("package.precompiled", false)
|
||||
|
||||
@@ -12,6 +12,19 @@ ROOT = ANDROID.parents[1]
|
||||
SOURCE = ROOT / 'deps/submodules/minirtc'
|
||||
CACHE = ANDROID / '.native'
|
||||
PACKAGE_REVISION = 'eda39de3fb99b420c168f1ab9ab2d1791e11b662'
|
||||
XMAKE_MIN_VERSION = (3, 1, 1)
|
||||
|
||||
|
||||
def find_xmake():
|
||||
xmake = os.environ.get('XMAKE_BIN') or shutil.which('xmake')
|
||||
minimum = '.'.join(map(str, XMAKE_MIN_VERSION))
|
||||
if not xmake:
|
||||
raise SystemExit(f'Xmake {minimum} or newer is required; install it or set XMAKE_BIN.')
|
||||
output = subprocess.check_output([xmake, '--version'], text=True)
|
||||
version = re.search(r'xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)', output)
|
||||
if not version or tuple(map(int, version.groups())) < XMAKE_MIN_VERSION:
|
||||
raise SystemExit(f'Use Xmake {minimum} or newer (set XMAKE_BIN to its executable).')
|
||||
return xmake
|
||||
|
||||
|
||||
def main():
|
||||
@@ -20,13 +33,7 @@ def main():
|
||||
ndk = sdk / 'ndk/28.2.13676358'
|
||||
if not ndk.is_dir():
|
||||
raise SystemExit('Install Android NDK 28.2.13676358 using SDK Manager.')
|
||||
bundled = Path.home() / '.cache/crossdesk/toolchains/xmake-3.1.1/xmake'
|
||||
xmake = os.environ.get('XMAKE_BIN') or (str(bundled) if bundled.is_file() else shutil.which('xmake'))
|
||||
if not xmake:
|
||||
raise SystemExit('xmake is required; install it or set XMAKE_BIN.')
|
||||
version = subprocess.check_output([xmake, '--version'], text=True)
|
||||
if not re.search(r'xmake v3\.1\.1(?:\+|[,\s])', version):
|
||||
raise SystemExit('Use Xmake 3.1.1 (set XMAKE_BIN to its executable).')
|
||||
xmake = find_xmake()
|
||||
if not (SOURCE / 'xmake.lua').is_file():
|
||||
raise SystemExit('Initialize the MiniRTC submodule before building.')
|
||||
env = os.environ.copy()
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Check Xmake discovery and the minimum supported version before native builds."""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
source = Path(__file__).resolve().parents[1] / "scripts/build_native.py"
|
||||
spec = importlib.util.spec_from_file_location("build_native", source)
|
||||
build_native = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(build_native)
|
||||
|
||||
|
||||
class XmakeSelectionTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
for patcher in (
|
||||
patch.dict(os.environ, {}, clear=True),
|
||||
patch.object(build_native.shutil, "which", return_value="/tools/xmake"),
|
||||
patch.object(build_native.subprocess, "check_output", return_value="xmake v3.1.1\n"),
|
||||
):
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_path_tool_accepts_minimum_and_newer_versions(self):
|
||||
for output in ("xmake v3.1.1", "xmake v3.1.2\n", "xmake v3.1.10, A build utility\n",
|
||||
"xmake v3.2.0\n", "xmake v3.10.0\n", "xmake v4.0.0\n",
|
||||
"\x1b[1mxmake v3.1.1+HEAD.3ba37a0d4, A build utility\x1b[0m\n"):
|
||||
with self.subTest(output=output):
|
||||
build_native.subprocess.check_output.return_value = output
|
||||
self.assertEqual(build_native.find_xmake(), "/tools/xmake")
|
||||
|
||||
def test_explicit_tool_takes_precedence_over_path(self):
|
||||
os.environ["XMAKE_BIN"] = "/custom/xmake"
|
||||
self.assertEqual(build_native.find_xmake(), "/custom/xmake")
|
||||
build_native.shutil.which.assert_not_called()
|
||||
build_native.subprocess.check_output.assert_called_once_with(
|
||||
["/custom/xmake", "--version"], text=True)
|
||||
|
||||
def test_missing_tool_reports_minimum_requirement(self):
|
||||
build_native.shutil.which.return_value = None
|
||||
with self.assertRaisesRegex(SystemExit, "Xmake 3.1.1 or newer is required"):
|
||||
build_native.find_xmake()
|
||||
build_native.subprocess.check_output.assert_not_called()
|
||||
|
||||
def test_older_or_unrecognized_versions_are_rejected(self):
|
||||
for output in ("xmake v3.1.0\n", "xmake v3.0.99\n", "xmake v2.99.99\n",
|
||||
"xmake version unknown\n", "xmake v3.1\n", "xmake v3.1.1invalid\n", ""):
|
||||
with self.subTest(output=output):
|
||||
build_native.subprocess.check_output.return_value = output
|
||||
with self.assertRaisesRegex(SystemExit, "Use Xmake 3.1.1 or newer"):
|
||||
build_native.find_xmake()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+7
-6
@@ -7,7 +7,7 @@ There is no `WKWebView` or browser runtime.
|
||||
## Requirements
|
||||
|
||||
- Xcode 16 or newer
|
||||
- Xmake 3.1.1 available on `PATH` (or selected with `XMAKE_BIN`)
|
||||
- Xmake 3.1.1 or newer available on `PATH` (or selected with `XMAKE_BIN`)
|
||||
- A physical arm64 iPhone/iPad running iOS 16 or newer, or an iOS simulator on Apple Silicon
|
||||
|
||||
## Build
|
||||
@@ -27,7 +27,7 @@ licenses** when cloning: `git clone --branch <source-tag> --recurse-submodules
|
||||
https://github.com/kunkundi/crossdesk.git`. The iOS marketing version is independent
|
||||
of the repository tag; use the source tag or commit recorded in the app.
|
||||
|
||||
Install [Xmake 3.1.1](https://github.com/xmake-io/xmake/releases/tag/v3.1.1),
|
||||
Install [Xmake](https://xmake.io/guide/quick-start.html) 3.1.1 or newer,
|
||||
CMake, and a current Python 3 with setuptools/wheel. Pin the package repository
|
||||
in a separate Xmake global directory before building:
|
||||
|
||||
@@ -41,7 +41,7 @@ git -C "$XMAKE_GLOBALDIR/.xmake/repositories/xmake-repo" checkout --detach 12110
|
||||
Keep `XMAKE_GLOBALDIR` set for command-line Xcode builds. For builds launched
|
||||
from the Xcode app, set it as a user-defined build setting on the target, or
|
||||
pin the default `~/.xmake/repositories/xmake-repo` instead. The build checks
|
||||
the tool version, package repository commit and reviewed recipe hashes.
|
||||
the minimum Xmake version, package repository commit and reviewed recipe hashes.
|
||||
Application dependencies are built from source, with Xmake precompiled
|
||||
package downloads disabled.
|
||||
|
||||
@@ -105,9 +105,10 @@ or relevant build configuration also trigger it. The main
|
||||
workflow on branch/tag pushes and manual runs, and waits for its result before
|
||||
publishing a version-tag release.
|
||||
|
||||
The workflow retains the pinned Xcode/SDK and Xmake versions, dependency cache,
|
||||
license and privacy checks, unsigned arm64 Release build, and app-bundle
|
||||
verification. It uploads `crossdesk-ios-arm64-unsigned-v<marketing-version>-<date>.zip`,
|
||||
The workflow tests the minimum supported Xmake version (3.1.1) and retains the
|
||||
pinned Xcode/SDK, dependency cache, license and privacy checks, unsigned arm64
|
||||
Release build, and app-bundle verification. It uploads
|
||||
`crossdesk-ios-arm64-unsigned-v<marketing-version>-<date>.zip`,
|
||||
including the app, source metadata, third-party source catalog, rights notice
|
||||
and any generated dSYM. The version comes from the iOS target; dated repository
|
||||
tags provide the build date, otherwise the date uses the Asia/Shanghai timezone.
|
||||
|
||||
@@ -900,7 +900,7 @@
|
||||
],
|
||||
"bundle_sha256": "f54e5e2168bf6e91d81a413931a052d29f59e0c221df5e1de3f51bd27c662c3a",
|
||||
"toolchain": {
|
||||
"xmake_version": "3.1.1",
|
||||
"xmake_min_version": "3.1.1",
|
||||
"xmake_repository_revision": "121109352da272cd1a3d4ed52f2dcda34f839955"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +137,10 @@ def generate(lock, cache):
|
||||
def verify(lock, inputs, target_info, xmake_repository=None, xmake_version=None):
|
||||
checked(OUTPUT.read_bytes(), lock["bundle_sha256"], str(OUTPUT))
|
||||
if xmake_version is not None:
|
||||
version = re.search(r"xmake v(\d+\.\d+\.\d+)", xmake_version.read_text())
|
||||
if not version or version[1] != lock["toolchain"]["xmake_version"]:
|
||||
raise ValueError("Xmake version differs from the reviewed iOS toolchain; see apps/ios/README.md")
|
||||
minimum = lock["toolchain"]["xmake_min_version"]
|
||||
version = re.search(r"xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)", xmake_version.read_text())
|
||||
if not version or tuple(map(int, version.groups())) < tuple(map(int, minimum.split("."))):
|
||||
raise ValueError(f"Xmake {minimum} or newer is required; see apps/ios/README.md")
|
||||
if xmake_repository is not None:
|
||||
revision = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
|
||||
|
||||
@@ -52,7 +52,7 @@ class LicenseValidationTest(unittest.TestCase):
|
||||
self.target.write_text(str(self.root / "packages/a/asio/1.0/abcd/include") + "\n")
|
||||
self.lock = {
|
||||
"bundle_sha256": licenses.digest(self.bundle.read_bytes()),
|
||||
"toolchain": {"xmake_version": "3.1.1"},
|
||||
"toolchain": {"xmake_min_version": "3.1.1"},
|
||||
"components": [
|
||||
{
|
||||
"id": "minirtc", "version": self.reviewed_revision,
|
||||
@@ -149,11 +149,23 @@ class LicenseValidationTest(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*libopus.a"):
|
||||
self.verify()
|
||||
|
||||
def test_unreviewed_toolchain_is_rejected(self):
|
||||
def test_minimum_and_newer_xmake_versions_are_accepted(self):
|
||||
version = self.root / "xmake-version.txt"
|
||||
version.write_text("xmake v3.1.2\n")
|
||||
with self.assertRaisesRegex(ValueError, "Xmake version differs"):
|
||||
self.verify(xmake_version=version)
|
||||
for output in ("xmake v3.1.1", "xmake v3.1.2\n", "xmake v3.1.10, A build utility\n",
|
||||
"xmake v3.2.0\n", "xmake v3.10.0\n", "xmake v4.0.0\n",
|
||||
"\x1b[1mxmake v3.1.1+HEAD.3ba37a0d4, A build utility\x1b[0m\n"):
|
||||
with self.subTest(output=output):
|
||||
version.write_text(output)
|
||||
self.verify(xmake_version=version)
|
||||
|
||||
def test_older_or_unrecognized_xmake_versions_are_rejected(self):
|
||||
version = self.root / "xmake-version.txt"
|
||||
for output in ("xmake v3.1.0\n", "xmake v3.0.99\n", "xmake v2.99.99\n",
|
||||
"xmake version unknown\n", "xmake v3.1\n", "xmake v3.1.1invalid\n", ""):
|
||||
with self.subTest(output=output):
|
||||
version.write_text(output)
|
||||
with self.assertRaisesRegex(ValueError, "Xmake 3.1.1 or newer is required"):
|
||||
self.verify(xmake_version=version)
|
||||
|
||||
|
||||
class ComponentSourceTest(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user