鎖文件
lip 使用鎖文件來維護當前工作區狀態。該文件會精確記錄已安裝的包、具體版本、包的來源,以及寫入磁盤的文件。
位置
鎖文件保存在工作區之外、lip 的用戶數據目錄中,因此工作區裡只有各個包的文件:
| 平臺 | 路徑 |
|---|---|
| Windows | %LOCALAPPDATA%\lip\workspaces\<hash>.json |
| Linux / macOS | $XDG_DATA_HOME/lip/workspaces/<hash>.json(默認 ~/.local/share/lip/workspaces/<hash>.json) |
<hash> 是工作區絕對路徑(不帶末尾分隔符;Windows 上路徑不區分大小寫,因此先轉為小寫)的 SHA-256,以小寫十六進制表示。鎖文件還會在 workspace 中記錄該路徑;若兩者不一致,lip 會報錯停止,而不會使用其他工作區的鎖。
由於鎖與路徑綁定,移動或重命名工作區目錄後,原來的鎖不會跟隨:lip 會把移動後的工作區視為未安裝任何包。
從工作區遷移
早期版本把鎖保存在工作區根目錄,即 lip_lock.json(格式 4)或 tooth_lock.json(格式 3)。當數據目錄中沒有某個工作區的鎖時,lip 會讀取 lip_lock.json,否則遷移 tooth_lock.json(參見下文)。下一次保存鎖時(安裝、卸載或更新包之後),lip 會把鎖寫入數據目錄,並從工作區刪除這兩個文件。
作用
鎖文件在 lip 中有幾個關鍵用途:
- 狀態追蹤:作為工作區當前安裝狀態的唯一真實來源。
- 可復現性:記錄每個已安裝包的確切歸檔。當 lip 再次需要某個包時(例如卸載它),會直接打開該歸檔並校驗其標識,而不是重新解析該包。
- 卸載清理:
lip會追蹤包寫入的每個文件,使lip uninstall能幹淨刪除相關文件,避免工作區出現“陳舊文件”。 - 依賴圖:區分用戶顯式安裝的包和作為依賴隱式安裝的包。這樣
lip uninstall會一併移除不再被任何顯式安裝的包需要的依賴(除非指定了--no-dependencies),lip list也能分組顯示這兩類包。 - 環境:一旦確定了工作區的平臺和 RID,就記錄下來,使之後的命令使用相同的環境。
鎖文件在一次操作的所有包都安裝或卸載完成之後保存一次。操作失敗時,lip 會撤銷它對工作區的修改,鎖文件保持原樣,因此兩者始終一致(參見內部機制)。
結構
鎖文件是一個 JSON 文件,由 JSON Schema lock.v4.schema.json 描述:
{
"formatVersion": 4,
"formatUuid": "0c6b977a-64ad-441f-9a29-c7f4b196882f",
"workspace": "C:\\bds",
"platform": "server",
"rid": "win-x64",
"packages": [
...
]
}字段
formatVersion:鎖文件格式版本號(當前為4)。formatUuid:用於確保文件類型正確的唯一標識。workspace:鎖文件所屬工作區的絕對路徑。platform:工作區的平臺。lip 寫入"server";從tooth_lock.json遷移而來的鎖在 lip 保存之前為null。rid:工作區的 .NET RID。packages:已安裝的包,按安裝順序排列。
包條目
-
name:包的身份:其manifest.json的name,或tooth.json包的tooth路徑,帶標籤的變體在其後加#<label>(例如github.com/LiteLDev/LegacyScriptEngine#lua)。早期版本寫入的鎖對帶標籤的變體只記錄了tooth路徑;lip 讀取時會補上標籤。 -
version:已安裝的版本。 -
kind:manifest.json包為"mcmod",tooth.json包為"tooth"。兩者的安裝與卸載方式相同;kind 說明manifest中保存的是什麼。 -
explicit:true:用戶顯式安裝(如lip install <package>)。false:作為其他包依賴被隱式安裝。
-
chain:包的解析鏈,每一跳一項,包含:ref:該跳解析時的 ref,例如github.com/user/repo、git 引用形式的github.com/user/repo@branch:main、https://URL、上一歸檔內的./sub/,或傳給lip install的本地文件的file:///URI;artifact:在該跳選中的路由條目下標(該跳不是路由文檔時為null);commit(僅包路徑):該跳倉庫所在的提交:版本標籤的提交,或 git 引用解析到的提交。早期版本寫下的鎖中沒有此項。
lip update會重新請求第一跳:分支跟隨到新的頭,標籤重新解析,rev 保持不變。 -
archiveKey:最終歸檔的鍵,例如github.com/user/repo@1.0.0;帶 git 引用的包路徑為github.com/user/repo@rev:<commit>。 -
archive:最終歸檔的標識:{ "kind": "zip", "sha256": ..., "url": ... }:ZIP 歸檔。url是下載它的https://URL、本地文件的file:///URI,或對嵌套在其他歸檔中的 ZIP 文件為null;包是 ZIP 文件內的一個目錄時,會設置path。{ "kind": "git", "repo": ..., "commit": ..., "path": ... }:git 倉庫的一個提交;倉庫根目錄的path為""。null:從tooth_lock.json遷移而來的條目。
lip 再次需要已安裝包的文件時,會打開該歸檔並校驗其標識:git 歸檔按記錄的提交獲取(即使它來自一個已經前進的分支),下載的或本地的 ZIP 文件必須仍具有記錄的 SHA-256。卸載則兩者都不需要。
-
files:該包擁有的文件,相對於工作區根目錄,以/分隔。服務端的plugins是指向mods的鏈接,因此plugins/下的文件記為mods/下的路徑;早期版本寫下的鎖中的plugins/路徑會在加載時被改寫。 -
manifest:包的文檔(manifest.json;tooth包為tooth.json,lip 需要該包時會再次把它轉換為第 4 版),這樣 lip 無需再次獲取。
示例
{
"name": "LegacyMoney",
"version": "0.22.0",
"kind": "mcmod",
"explicit": true,
"chain": [
{ "ref": "github.com/LiteLDev/LegacyMoney", "artifact": 0 },
{
"ref": "https://github.com/LiteLDev/LegacyMoney/releases/download/v0.22.0/LegacyMoney-server-win-x64.mcmod",
"artifact": null
}
],
"archiveKey": "https://github.com/LiteLDev/LegacyMoney/releases/download/v0.22.0/LegacyMoney-server-win-x64.mcmod",
"archive": {
"kind": "zip",
"sha256": "…",
"url": "https://github.com/LiteLDev/LegacyMoney/releases/download/v0.22.0/LegacyMoney-server-win-x64.mcmod"
},
"files": [
"mods/LegacyMoney/LegacyMoney.dll",
"mods/LegacyMoney/manifest.json"
],
"manifest": { "...": "..." }
}舊格式:tooth_lock.json
lip v0.x 在工作區根目錄使用 tooth_lock.json(格式 3)。若數據目錄中沒有鎖、工作區中也沒有 lip_lock.json,但有 tooth_lock.json,lip 會在加載時遷移它,在下一次保存時把鎖寫入數據目錄並刪除 tooth_lock.json。遷移而來的條目類型為 "tooth",沒有 archive;當 lip 需要它們的文件時,會重新解析標籤 path@version。
舊格式由 workspace_state.schema.json 描述:
{
"format_version": 3,
"format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d",
"packages": [
{
"manifest": { "...": "the tooth.json" },
"variant": "",
"locked": true,
"files": ["bin/example.exe"]
}
]
}manifest:包的完整tooth.json。variant:已安裝變體的標籤。locked:包為顯式安裝時為true。files:包寫入的文件。