Skip to content

Uni 建置系統 ​

Uni 是 uwuAOSP 的本機建置調度器。它仍使用 Soong、Kati 和 Ninja 產生並執行 Android 建置規則,但會在執行前保存建置圖狀態、拆分關鍵階段,並依即時資源為高記憶體工作分配獨立資源池。

sh
source build/envsetup.sh
lunch uwu_nabu-cp2a-userdebug
uni -j18 otapackage

模組增量建置使用相同入口:

sh
uni -j18 SystemUI Settings Launcher3QuickStep

工作原理 ​

一次完整建置分為準備、啟動和最終 Ninja 階段:

  1. 準備:檢查產品、release、variant、目標參數、環境和建置系統指紋。指紋未變更時重用已保存的 Soong/Blueprint 建置圖;變更時才重新產生 Android.bp 圖。
  2. 建立工作集:從產品 Ninja 圖讀取可建置目標,結合歷史耗時、R8 目標和核心目標計算啟動順序。長工作會提前處理,R8 目標分散到批次中,避免最後集中排隊。
  3. 啟動階段:完整建置優先處理需要先完成的核心或歷史長工作。核心連結會與 Kotlin、Java、R8 等高記憶體工作分開,減少同時換頁。
  4. 主要執行:剩餘目標交給 Ninja 執行。全域 -j 仍由使用者指定,Java、Kotlin、Rust、R8 和其他高記憶體工作透過環境變數使用各自的准入池。
  5. 最終階段:驗證建置圖沒有在過程中變更,再執行封裝、分發和 OTA 目標。每個階段完成後寫入狀態,下一次建置可以重用已完成的輸出。
  6. 復原:發生中斷或持續記憶體壓力時,保留狀態和現有輸出。復原執行會重新讀取磁碟狀態,不會刪除可重用產物;只有確認出現壓力後才會降低自動資源池。

Uni 不會取代相依性檢查,也不會隱藏真實的原始碼變更。它只減少重複準備和不必要的等待。

相較 Make 的改進 ​

原生 make/m 將整個 Ninja 圖交給單一全域並行限制。Uni 在此基礎上增加:

  • 建置圖重用:記錄產品、版本、變體、目標參數、圖檔案狀態和建置原始碼指紋,在條件不變時跳過重複準備。
  • 關鍵路徑提前:依歷史工作耗時優先處理長工作,並將 R8 目標分散到多個階段,減少尾端集中等待。
  • 核心階段隔離:完整建置先執行必要的巢狀核心目標,避免核心連結和 R8、Kotlin 同時爭用記憶體。
  • 獨立資源池:R8、Java、Kotlin、Rust 和其他高記憶體工作分別計算並行數,Ninja 全域 -j 不會被改寫。
  • Rust 並行限制:同時考慮 rustc 數量和每個程序的 codegen units,避免 LLVM 後端讓一個 Ninja 工作再次放大成過多執行緒。
  • 分段復原:階段完成後保存 Ninja 復原狀態。受到壓力中止後,已完成輸出仍可重用。
  • 可觀測性:記錄 ccache 命中,並追蹤磁碟空間、記憶體、swap、PSI、CPU、程序峰值和資源池決策。
  • 終端介面:顯示 Graph、Kernel、Startup、Main、R8 和記憶體狀態,支援詳細資訊、複製模式和完整中斷。

資源計算公式 ​

Uni 使用 /proc/meminfo 的 MemTotal 和 MemAvailable。以下數值均以 GiB 計算:

text
T = MemTotal
A = MemAvailable
R = max(4, 0.25 × T)
H = min(6, max(4, 0.20 × T))

R 是保留給 Soong 非 Go 配置、系統和檔案快取的記憶體,H 是選擇 Android.bp 分析堆上限 G 時保留的活動程序空間:

text
L = T - R
若 A > H:G = min(L, max(A / 2, A - H))
若 A ≤ H:G = A / 2
若結果 ≤ 0:G = max(1, T / 2)

G 不是單一 soong_build 程序的硬性 RSS 上限。C/C++、檔案映射和工具鏈程序也會使用記憶體,因此必須保留非 Go 記憶體空間。

工作資源池 ​

對每一種工作,B 是並行准入預算,J 是使用者的全域 Ninja 限制:

text
P = max(1, min(J, floor((A - 3 GiB) / B)))
工作B
Java2 GiB
Rust2 GiB
其他高記憶體工作4 GiB
R85 GiB
Kotlin5 GiB

Rust 還會限制 LLVM codegen units。若 C 是 codegen units,Rust 池不會超過:

text
min(PRust, ceil(J / C))

明確設定 NINJA_HIGHMEM_NUM_JOBS、NINJA_UNI_R8_NUM_JOBS、NINJA_UNI_RUST_NUM_JOBS、NINJA_UNI_JAVA_NUM_JOBS 或 NINJA_UNI_KOTLIN_NUM_JOBS 時,對應資源池會使用使用者提供的值。沒有設定時,每個階段都會重新讀取 MemAvailable 計算,不會為所有電腦寫死同一組並行數。

建置圖與增量復原 ​

正常重用要求以下內容保持相容:

  • 產品、release、variant 和目標參數
  • Android.bp、Blueprint、Make、Soong、Blueprint 和產品設定指紋
  • 已產生的 Ninja 圖及其輸入檔案狀態
  • 建置器版本和相關環境變數

原始碼或建置規則變更時,Uni 會重新準備受影響的圖。只有在確認磁碟輸出與目前原始碼相符時,才使用復原參數:

參數行為
--trust-output跳過復原輸出的新鮮度驗證
--assume-existing接受 Ninja 記錄遺失但磁碟存在的輸出,並啟用 --trust-output
--force-reuse跳過原始碼新鮮度檢查,強制重用保存的圖

這些參數不會改變相依性關係。產品、分支或建置規則變更後,應使用普通增量建置讓 Uni 更新建置圖。

常用指令 ​

sh
# 檢視調度,不執行 Ninja
uni --plan -j18 otapackage

# 本次重新產生完整 R8 索引
uni --dev -j18 otapackage

# 持續啟用 R8 索引自動更新
uni --dev-auto -j18 otapackage

# 清理 Uni/Soong 建置記錄,不刪除建置輸出
uni --clean-logs

# 本次停用詳細除錯報告
uni --no-debug -j18 SystemUI

簽名發佈 OTA ​

在 Android 原始碼樹中一次性初始化金鑰。目標目錄必須位於原始碼樹外,而且事先不能存在。不需要 lunch 或建置:

sh
uni --init-signing-keys ~/.android-certs

Uni 會產生 LineageOS 基礎金鑰:bluetooth、cyngn-app、media、networkstack、nfc、platform、releasekey、sdk_sandbox、shared、testcert 和 verity;testkey 會指向 releasekey。私鑰為未加密的 PKCS#8 檔案。目錄權限為 0700,金鑰檔案權限為 0600。若目標目錄已存在,命令會失敗;絕不覆蓋手動產生或先前發佈使用的金鑰。

請將整個金鑰目錄安全備份至原始碼樹和建置磁碟以外的位置。刪除原始碼樹或 out 不會影響存放在外部的金鑰;若金鑰遺失,新金鑰無法直接延續信任舊金鑰裝置的一般 OTA 更新。

完成 lunch 後,每次發佈都使用同一目錄。Uni 會增量建置 target-files-package 和 otatools,再於獨立目錄簽署 target-files 並生成 OTA:

sh
uni -j18 otapackage --sign-keys ~/.android-certs

已有手動產生的金鑰目錄可以直接傳給 --sign-keys,無須初始化,Uni 也不會修改它。Uni 初始化的目錄會在首次簽名時讀取 META/apexkeys.txt,為非 PRESIGNED APEX 產生 4096 位元金鑰。後續發佈會重用這些金鑰;新增 APEX 只會增加新金鑰,不會替換既有金鑰。備份時也請包含 apex/ 子目錄。

輸出位於 OUT_DIR/release/<product>/,包含加上時間戳的簽名 target-files、簽名 OTA 和 OTA 的 .sha256 驗證檔;不會覆蓋一般 OTA。簽名時會使用建置樹中的 hermetic sign_target_files_apks 和 ota_from_target_files,不會使用舊式 --block --backup=true 參數。

APEX 與非預設 APK 金鑰 ​

--sign-keys 會傳入標準的 -o -d <keys> 對應。手動管理的金鑰目錄、非預設 APK 或需要指定其他 APEX 金鑰的裝置,可另外準備並長期保存對應金鑰,再提供 JSON 設定:

json
{
  "key_mappings": {
    "source/key/path": "custom-key"
  },
  "extra_apks": {
    "com.android.example.apex": "releasekey"
  },
  "extra_apex_payload_keys": {
    "com.android.example.apex": "apex-payload-key.pem"
  }
}

設定中的相對路徑以 --sign-keys 目錄為基準。APEX payload 私鑰必須符合對應 APEX 的簽名需求,不能用上述 2048 位元基礎 APK 金鑰取代;此設定不會自動替換 AVB 金鑰:

sh
uni -j18 otapackage --sign-keys ~/.android-certs --sign-config signing.json

隔離簽名檢查 ​

先對既有 target-files 完整測試簽名和 OTA 生成,不發布正式套件:

sh
uni --sign-keys ~/.android-certs --sign-check

檢查結果寫入 OUT_DIR/release/<product>/checks/<timestamp>/。此模式不會啟動 Android 建置、不修改一般產物,也不會覆蓋正式簽名套件。--sign-keys 不能與 --trust-output 或 --assume-existing 同時使用。

使用 test-key 的裝置不能將 release-key OTA 當作一般增量更新接收。首次更換金鑰時,必須透過信任新金鑰的 recovery 或 fastboot 完整刷入,或使用另外準備並驗證過的金鑰遷移套件。

Uni 執行時遙測 ​

Uni 运行时遥测展示一次成功 Uni 构建最终 82 分 37 秒内 CPU、可用内存、累计 swap-out 与 I/O wait 的遥测图表。最终阶段 | 82 分 37 秒CPU · 平均 96.2%可用内存 · 最低 5.7 GiBswap-out · 累计 20.1 GiBI/O waitCPU / 可用内存100%66%33%0%30 GiB20 GiB10 GiB0 GiBswap-out / I/O wait22 GiB15 GiB7 GiB0 GiB50%33%16%0%0m20m40m60m82m阶段时间
采样自 2026-09-14 一次成功 Uni 构建的最终阶段。CPU、MemAvailable、swap-out 和 I/O wait 均来自日志 telemetry 字段。

圖表依 telemetry 欄位繪製 CPU、MemAvailable、累計 swap-out 和 iowait。橫軸是最終建置階段的經過時間。

輸出與疑難排解 ​

建置完成時,Uni 會輸出一份摘要,包括階段數、最低可用記憶體、swap-out、套件或輸出目錄和總耗時。記錄會寫入 OUT_DIR:

text
uwuCli-output_<timestamp>.log
uwuCli-debug-report_<timestamp>.log

失敗時先讀取記錄中的第一個真實編譯錯誤。最後的 FAILED: ninja 或 signal: killed 只說明最終狀態,不能單獨用來確定根因。