Lock-файл

From docs/ru/LOCKFILE.md at 4e43843 View on GitHub

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имя, под которым пакет установлен
sourceowner/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 запустить не может. Поэтому:

  • На машине с совпадающим target sync сверяет загрузку с записанным 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.