소스 검색

build: 收紧平台基线并启用 -O3 thin LTO

停打 Intel Mac,macOS 最低 13.0 且仅 Apple Silicon。
Windows 要求 2004+ x64 AVX2,Linux 仅 x86_64 AVX2。
正式包改用 opt-level=3 与 thin LTO,codegen-units 保持 16。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi 1 주 전
부모
커밋
75bef285a1

+ 3 - 17
.github/workflows/build.yml

@@ -74,9 +74,7 @@ jobs:
     if: github.event_name == 'push'
     runs-on: ${{ matrix.runner }}
     env:
-      CARGO_PROFILE_RELEASE_LTO: "false"
-      CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
-      CARGO_PROFILE_RELEASE_OPT_LEVEL: "1"
+      MACOSX_DEPLOYMENT_TARGET: "13.0"
     strategy:
       fail-fast: false
       matrix:
@@ -99,18 +97,10 @@ jobs:
               src-tauri/target/**/release/bundle/dmg/*.dmg
               release-assets/*.app.tar.gz
 
-          - label: macos-intel
-            display_label: macOS Intel
-            runner: macos-15-intel
-            rust_targets: "x86_64-apple-darwin"
-            tauri_args: "--target x86_64-apple-darwin --bundles dmg,app --no-sign"
-            artifact_globs: |
-              src-tauri/target/**/release/bundle/dmg/*.dmg
-              release-assets/*.app.tar.gz
-
           - label: linux-x64
             display_label: Linux x64
             runner: ubuntu-22.04
+            # x86_64 / amd64 only. Do not add i686 or aarch64 Linux targets.
             rust_targets: ""
             tauri_args: "--bundles deb,appimage"
             artifact_globs: |
@@ -259,11 +249,7 @@ jobs:
         run: |
           set -euo pipefail
           version="$(node -p "require('./package.json').version")"
-          if [ "${{ matrix.label }}" = "macos-intel" ]; then
-            system_label="macOS_Intel"
-          else
-            system_label="macOS_AppleSilicon"
-          fi
+          system_label="macOS_AppleSilicon"
           mkdir -p release-assets
 
           dmg_path="$(find src-tauri/target -path '*/release/bundle/dmg/*.dmg' -print -quit)"

+ 2 - 7
.github/workflows/ci.yml

@@ -108,15 +108,14 @@ jobs:
     if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
     runs-on: ${{ matrix.platform }}
     env:
-      CARGO_PROFILE_RELEASE_LTO: "false"
-      CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
-      CARGO_PROFILE_RELEASE_OPT_LEVEL: "1"
+      MACOSX_DEPLOYMENT_TARGET: "13.0"
     strategy:
       fail-fast: false
       matrix:
         include:
           - platform: ubuntu-22.04
             display: Linux x64
+            # x86_64 / amd64 only. Do not add i686 or aarch64 Linux targets.
           - platform: macos-latest
             display: macOS
 
@@ -168,10 +167,6 @@ jobs:
     needs: changes
     if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
     runs-on: windows-latest
-    env:
-      CARGO_PROFILE_RELEASE_LTO: "false"
-      CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
-      CARGO_PROFILE_RELEASE_OPT_LEVEL: "1"
     steps:
       - name: Checkout
         uses: actions/checkout@v5

+ 0 - 4
.github/workflows/prerelease.yml

@@ -26,10 +26,6 @@ jobs:
   build-windows-prerelease:
     name: Build Windows x64 内测包
     runs-on: windows-latest
-    env:
-      CARGO_PROFILE_RELEASE_LTO: "false"
-      CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
-      CARGO_PROFILE_RELEASE_OPT_LEVEL: "1"
     steps:
       - name: Checkout
         uses: actions/checkout@v5

+ 4 - 1
README.md

@@ -338,7 +338,10 @@ AI 生成的章节默认为草稿状态。草稿支持预览、编辑、重新
 
 ### 环境要求
 
-- **操作系统**:Windows 10+(主要支持平台)、macOS、Linux
+- **操作系统**:
+  - **Windows**:10 2004 (20H1) / Windows 11,**仅 x64**,CPU 需支持 **AVX2**(Intel Haswell / AMD Excavator 或更新)。需要系统已安装 WebView2。不支持 32 位。
+  - **macOS**:13.0+(Ventura),**仅 Apple Silicon(M1 起)**。不提供 Intel 包。
+  - **Linux**:**仅 x86_64 / amd64**,CPU 需支持 **AVX2**。Ubuntu 22.04+(webkit2gtk-4.1 / glibc 2.35)。不支持 32 位,不提供 ARM64 包。
 - **LLM 服务**:需配置至少一个大语言模型 API(支持 OpenAI 兼容接口、Ollama 等)
 
 ### 安装方式

+ 2 - 2
package.json

@@ -14,8 +14,8 @@
     "lint:character-aura": "node scripts/lint-character-aura.mjs",
     "test:llm": "vitest run real-llm --no-file-parallelism --reporter=verbose",
     "tauri": "tauri",
-    "build:portable": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"$env:CARGO_PROFILE_RELEASE_LTO='false'; $env:CARGO_PROFILE_RELEASE_CODEGEN_UNITS='16'; $env:CARGO_PROFILE_RELEASE_OPT_LEVEL='1'; npx.cmd tauri build --no-bundle; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/build-portable.mjs\"",
-    "build:github-release": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"if (-not $env:TAURI_SIGNING_PRIVATE_KEY_PATH) { $env:TAURI_SIGNING_PRIVATE_KEY_PATH = Join-Path $env:USERPROFILE '.tauri\\qmai-updater.key' }; if (-not (Test-Path $env:TAURI_SIGNING_PRIVATE_KEY_PATH)) { throw '缺少 TAURI_SIGNING_PRIVATE_KEY_PATH 对应的签名私钥文件' }; if ($null -eq $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD) { $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = '' }; $env:CARGO_PROFILE_RELEASE_LTO='false'; $env:CARGO_PROFILE_RELEASE_CODEGEN_UNITS='16'; $env:CARGO_PROFILE_RELEASE_OPT_LEVEL='1'; npx tauri build; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/build-portable.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/prepare-github-release.mjs\""
+    "build:portable": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"npx.cmd tauri build --no-bundle; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/build-portable.mjs\"",
+    "build:github-release": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"if (-not $env:TAURI_SIGNING_PRIVATE_KEY_PATH) { $env:TAURI_SIGNING_PRIVATE_KEY_PATH = Join-Path $env:USERPROFILE '.tauri\\qmai-updater.key' }; if (-not (Test-Path $env:TAURI_SIGNING_PRIVATE_KEY_PATH)) { throw '缺少 TAURI_SIGNING_PRIVATE_KEY_PATH 对应的签名私钥文件' }; if ($null -eq $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD) { $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = '' }; npx tauri build; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/build-portable.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; node scripts/prepare-github-release.mjs\""
   },
   "dependencies": {
     "@base-ui/react": "^1.7.0",

+ 5 - 0
src-tauri/.cargo/config.toml

@@ -0,0 +1,5 @@
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-C", "target-cpu=x86-64-v3"]
+
+[target.x86_64-unknown-linux-gnu]
+rustflags = ["-C", "target-cpu=x86-64-v3"]

+ 3 - 3
src-tauri/Cargo.toml

@@ -72,9 +72,9 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = ["s
 tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
 
 [profile.release]
-codegen-units = 1
-lto = true
-opt-level = "z"
+codegen-units = 16
+lto = "thin"
+opt-level = 3
 # Unwind (not abort) so third-party parser panics can be caught at the
 # Tauri command boundary via panic_guard and turned into errors. Slightly
 # larger binary, but prevents single-file corruption from killing the app.

+ 3 - 0
src-tauri/src/lib.rs

@@ -2,9 +2,12 @@ mod app_state;
 mod atomic_file;
 mod commands;
 mod panic_guard;
+mod platform_guard;
 mod proxy;
 mod types;
 
+pub use platform_guard::assert_supported_platform;
+
 #[tauri::command]
 fn set_proxy_env(config: proxy::ProxyConfig) -> String {
     let summary = proxy::apply_proxy_env(&config);

+ 1 - 0
src-tauri/src/main.rs

@@ -1,5 +1,6 @@
 #![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
 
 fn main() {
+    qmai::assert_supported_platform();
     qmai::run();
 }

+ 124 - 0
src-tauri/src/platform_guard.rs

@@ -0,0 +1,124 @@
+//! Refuse to start on platforms below the published support floor.
+//!
+//! Windows: 10 2004 (build 19041)+, x64, AVX2.
+//! Linux x86_64: AVX2. 32-bit kernels cannot load the shipped ELF.
+
+pub fn assert_supported_platform() {
+    #[cfg(all(target_arch = "x86_64", not(target_os = "macos")))]
+    assert_avx2();
+
+    #[cfg(windows)]
+    assert_windows_version();
+}
+
+#[cfg(all(target_arch = "x86_64", not(target_os = "macos")))]
+fn assert_avx2() {
+    if !is_x86_feature_detected!("avx2") {
+        abort_unsupported(
+            "青幕AI写作需要支持 AVX2 的 64 位 CPU(Intel Haswell / AMD Excavator 或更新)。",
+        );
+    }
+}
+
+#[cfg(windows)]
+const MIN_WINDOWS_BUILD: u32 = 19041;
+
+#[cfg(windows)]
+fn assert_windows_version() {
+    if windows_build_number() < MIN_WINDOWS_BUILD {
+        abort_unsupported("青幕AI写作需要 Windows 10 2004 (20H1) 或更高版本。");
+    }
+}
+
+#[cfg(windows)]
+fn windows_build_number() -> u32 {
+    #[repr(C)]
+    struct OsVersionInfoW {
+        dw_os_version_info_size: u32,
+        dw_major_version: u32,
+        dw_minor_version: u32,
+        dw_build_number: u32,
+        dw_platform_id: u32,
+        sz_csd_version: [u16; 128],
+    }
+
+    #[link(name = "ntdll")]
+    extern "system" {
+        fn RtlGetVersion(info: *mut OsVersionInfoW) -> i32;
+    }
+
+    let mut info = OsVersionInfoW {
+        dw_os_version_info_size: std::mem::size_of::<OsVersionInfoW>() as u32,
+        dw_major_version: 0,
+        dw_minor_version: 0,
+        dw_build_number: 0,
+        dw_platform_id: 0,
+        sz_csd_version: [0; 128],
+    };
+    // SAFETY: `info` is a well-formed OSVERSIONINFOW; RtlGetVersion only writes it.
+    let status = unsafe { RtlGetVersion(&mut info) };
+    if status < 0 {
+        return 0;
+    }
+    info.dw_build_number
+}
+
+fn abort_unsupported(message: &str) -> ! {
+    eprintln!("{message}");
+    #[cfg(windows)]
+    show_windows_error(message);
+    std::process::exit(1);
+}
+
+#[cfg(windows)]
+fn show_windows_error(message: &str) {
+    use std::os::windows::ffi::OsStrExt;
+
+    let text: Vec<u16> = std::ffi::OsStr::new(message)
+        .encode_wide()
+        .chain(std::iter::once(0))
+        .collect();
+    let title: Vec<u16> = std::ffi::OsStr::new("青幕AI写作")
+        .encode_wide()
+        .chain(std::iter::once(0))
+        .collect();
+
+    #[link(name = "user32")]
+    extern "system" {
+        fn MessageBoxW(
+            hwnd: *mut core::ffi::c_void,
+            text: *const u16,
+            caption: *const u16,
+            ty: u32,
+        ) -> i32;
+    }
+
+    const MB_ICONERROR: u32 = 0x0000_0010;
+    // SAFETY: `text` and `title` are NUL-terminated UTF-16 buffers that live for the call.
+    unsafe {
+        MessageBoxW(
+            std::ptr::null_mut(),
+            text.as_ptr(),
+            title.as_ptr(),
+            MB_ICONERROR,
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #[cfg(windows)]
+    #[test]
+    fn windows_2004_floor_is_build_19041() {
+        assert_eq!(super::MIN_WINDOWS_BUILD, 19041);
+    }
+
+    #[cfg(all(target_arch = "x86_64", not(target_os = "macos")))]
+    #[test]
+    fn host_ci_machines_have_avx2() {
+        assert!(
+            is_x86_feature_detected!("avx2"),
+            "x86_64 Win/Linux builds assume AVX2; this host cannot run the release binary"
+        );
+    }
+}

+ 7 - 0
src-tauri/tauri.macos.conf.json

@@ -0,0 +1,7 @@
+{
+  "bundle": {
+    "macOS": {
+      "minimumSystemVersion": "13.0"
+    }
+  }
+}

+ 11 - 0
src-tauri/windows/_tauri-installer-template.nsi

@@ -22,6 +22,7 @@ ManifestDPIAwareness PerMonitorV2
 !include MUI2.nsh
 !include FileFunc.nsh
 !include x64.nsh
+!include WinVer.nsh
 !include WordFunc.nsh
 !include "utils.nsh"
 !include "FileAssociation.nsh"
@@ -476,6 +477,16 @@ FunctionEnd
 {{/each}}
 
 Function .onInit
+  ${IfNot} ${RunningX64}
+    MessageBox MB_OK|MB_ICONSTOP "青幕AI写作仅支持 64 位 Windows,不支持 32 位系统。"
+    Abort
+  ${EndIf}
+
+  ${IfNot} ${AtLeastBuild} 19041
+    MessageBox MB_OK|MB_ICONSTOP "青幕AI写作需要 Windows 10 2004 (20H1) 或更高版本。"
+    Abort
+  ${EndIf}
+
   ${GetOptions} $CMDLINE "/P" $PassiveMode
   ${IfNot} ${Errors}
     StrCpy $PassiveMode 1