Lock-файл
ketch.lock — это набор утилит одной машины, закреплённых на точных релизах. Запишите его,
закоммитьте рядом со своими dotfiles, и любая другая машина сможет воспроизвести тот же
набор:
ketch lock # записать ./ketch.lock по тому, что установлено
ketch lock --check # не разошлось ли дерево с ним?
ketch sync # установить то, что названо в lock-файле, в этих версиях
Его JSON Schema, сгенерированная из типов, в которые ketch его читает, — lock.schema.json.
Это не та блокировка, которую ketch берёт во время работы, — та является мьютексом над деревом установки, удерживается на время выполнения команды и освобождается в конце.
Как он выглядит
# ketch.lock — generated by `ketch lock`. Do not edit it by hand:
# run `ketch lock` again instead. Commit it.
version = 1
[[package]]
name = "ripgrep"
source = "github:BurntSushi/ripgrep"
version = "14.1.1"
tag = "14.1.1"
target = "macos-aarch64"
asset = "ripgrep-14.1.1-aarch64-apple-darwin.tar.gz"
sha256 = "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e"
[[package]]
name = "fd"
source = "github:sharkdp/fd"
version = "10.2.0"
tag = "v10.2.0"
target = "macos-aarch64"
asset = "fd-v10.2.0-aarch64-apple-darwin.tar.gz"
sha256 = "d3f0e1a5b3ee2b6d0a1a6d0dd6c72fd0e69f6bb7c5b3a2a94f0e5b9a2f8b8c11"
pinned = true
Пакеты записываются отсортированными по имени, поэтому файл стабилен, а его диффы
читаемы. pinned записывается только когда он равен true, а bin — только когда
выбор был сделан.
| Ключ | Что это |
|---|---|
name | имя, под которым пакет установлен |
source | owner/repo со схемой — стабильная идентичность пакета |
version | для людей; на деле разрешается tag |
tag | точный релиз, который запрашивает ketch sync |
target | машина, на которой была снята эта запись |
asset | файл релиза, взятый на этом target |
sha256 | этого файла |
pinned | был ли пакет закреплён на этой версии |
bin | бинарник, выбранный, когда релиз содержит несколько файлов с именем пакета, в виде имени файла (rtok-cli); ketch sync повторяет этот выбор, не спрашивая, — см. раздел bin в MANIFESTS.md |
published | для пакета на плавающем теге, который не является версией (nightly), — когда была опубликована зафиксированная сборка; пишется только для таких пакетов |
Что воспроизводимо, а что нет
Тег воспроизводим. Каждая машина, разрешающая один и тот же тег, получает один и тот же релиз. Ради этого его и записывают.
Ассет и его хеш воспроизводимы только на том же target. Lock-файл,
записанный на Apple Silicon, называет tarball aarch64, который машина
x86_64 Linux запустить не может. Поэтому:
- На машине с совпадающим
targetsyncсверяет загрузку с записаннымsha256и отклоняет её до распаковки чего-либо, если они не совпадают. Релиз, подменённый под уже опубликованным тегом, — это ровно то, что lock-файл и должен ловить. - На любой другой машине
syncвыбирает ассет, подходящий хосту, и проверяет его по контрольной сумме, которую публикует источник, как при обычной установке. Делать вид, что записанный хеш всё ещё действует, значило бы дать гарантию, которая втихую ею не является. - Плавающий тег (
nightly,dev) переопубликуют под тем же именем, поэтому сам тег сборку не удерживает. На записавшем target её уже фиксирует хеш. На любой другой машинеsyncсравниваетpublishedс тем, когда была опубликована сборка, на которую тег указывает сейчас, и отклоняет более позднюю ещё до загрузки; чтобы принять её, снова запуститеketch lock. Если источник не сообщает, когда опубликован релиз, остаётся только хеш на записавшем target.
Пакет local: воспроизводим только там, где есть его путь. Lock-файл записывает
абсолютный путь, откуда пакет установлен, и хеш того, что там лежало, — байтов
файла, а для бандла .app — дайджест каждого файла и симлинка дерева.
Каждая запись обрамлена видом, путём, битами прав и либо байтами файла, либо
собственной целью симлинка, так что два расположения не могут разделить один
дайджест, а перенацеливание симлинка или снятие +x его меняет. sync
переустанавливает из этого пути и отклоняет его ещё до размещения чего-либо, если
содержимое изменилось с момента записи lock-файла. Lock-файл, записанный до
этого обрамления, не совпадает с неизменным бандлом; запустите ketch lock
ещё раз, чтобы записать новый дайджест. На машине без этого пути
запись падает, как любой другой недоступный источник.
ketch sync
ketch sync # установить недостающее или стоящее не в той версии
ketch sync --dry-run # показать план, ничего не меняя
ketch sync --prune # также удалить пакеты, не названные в lock-файле
ketch sync --file <FILE> # lock-файл не в ./ketch.lock, а в другом месте
План читается как дифф:
+ jq 1.7.1
~ ripgrep 14.1.0 -> 14.1.1
- httpie not in the lockfile
2 already match
Пакет, не упомянутый в lock-файле, сам по себе не является расхождением — lock-файл
записывает то, что вы хотите, а не обязательно всё, что у вас есть, — поэтому
ketch lock --check игнорирует лишнее, и удаляет его только --prune. Удаление
может уничтожить чью-то работу, поэтому сначала задаётся вопрос, если не передан --yes.
pinned восстанавливается после установки, так что пакет, записанный в lock-файле как закреплённый,
возвращается закреплённым, а не тихо обновляемым.
Что проверяет ketch
Lock-файл — это файл, который мог написать кто-то другой; именно это и означает общий
репозиторий dotfiles. Поэтому ничему в нём не позволено выбирать путь в файловой
системе. sync запрашивает источник на теге, устанавливает его под записанным name
— которое должно пройти первую проверку ниже — и даёт обычному разрешению манифестов
решить, какие бинарники ставить и куда; записанный bin лишь решает,
какой из нескольких бинарников с именем пакета получит ссылку, после любого выбора,
который уже запомнил state.json. Lock-файл закрепляет, какой релиз, но никогда — куда он
попадёт.
| Отклоняется | Почему |
|---|---|
name, которое нельзя использовать дословно как один компонент пути | оно сопоставляется с установленными пакетами и показывается вам; имя, которое пришлось бы переписать, означает не то, что в нём написано |
| один и тот же пакет дважды | установить можно было бы только один из них |
источник github:, не являющийся корректным owner/repo | он превращается в URL |
| любой другой источник с пустым id | разрешать нечего |
sha256, не состоящий из 64 шестнадцатеричных символов | он сравнивается с настоящим дайджестом |
bin, пустой или непригодный дословно как одно имя файла | он называет файл в релизе; значение с разделителем пути, .. или управляющими символами не может этого делать |
пустой tag | разрешать нечего |
published, пустой или содержащий что-либо кроме печатного ASCII | он выводится в ошибке, которая называет сдвинувшийся тег |
target, который ketch не распознаёт | он молча превращает запись в кросс-платформенную, так что записанные ассет и хеш перестают действовать, а хеш, изменившийся под тегом, выглядит чистым |
| неизвестный ключ | ключ с опечаткой, который молча игнорируется, закрепляет не то, что вы написали |
version новее, чем понимает этот ketch | обновитесь через ketch self upgrade |
Одна плохая запись проваливает весь файл, а не пропускается. В отличие от реестра, где частичный ответ лучше никакого, lock-файл, установивший большую часть себя, вовсе не был бы lock-файлом.
Какое имя разрешается
sync сначала запрашивает пакет по name, потому что изначально он был найден
именно так, а то, какой уровень манифестов ответит, решает, что получит ссылки и под
какими именами. Разрешение напрямую из github:BurntSushi/ripgrep провалилось бы
до вывода по репозиторию и могло бы дать другие бинарники, чем запись реестра,
которую вы на самом деле установили.
Имя используется только пока оно означает тот же проект. Если теперь оно разрешается
в другой источник, побеждает source из lock-файла — имя, сменившее
владельца, не должно тихо установить что-то другое.
В любом случае пакет устанавливается под name из lock-файла. Пакет,
установленный через ketch install --name, возвращается под этим именем, а не под тем, которое вывел бы
его источник, так что ketch lock --check согласуется с машиной, которую только что настроил sync.