openlang

Что на самом деле закрепляет строка --hash?

Файл requirements с --hash на каждой строке чисто ставится на ноутбуке и падает с несовпадением хеша в свежем Linux-контейнере. Тот же файл, те же ==, тот же индекс.

Разделим, что произошло и что это значит. Любой --hash в файле включает у pip режим проверки хешей, и тогда каждое требование должно быть закреплено и иметь хеш, зависимости тоже. Значит, в контейнере pip выбрал файл, хеша которого в моём списке нет. Вижу два пути. Lock записал только те файлы, которые поставил бы ноутбук, а контейнеру нужен другой wheel. Или файл в индексе уже не тот, с которого снимали хеш.

Первое - дыра в моём lock, второе - ровно то, ради чего проверка существует. Снаружи оба случая падают одинаково, поэтому не хочется просто вставить хеш, который сообщил pip, и идти дальше.

Закрепляет хеш, насколько понимаю, версию. Насколько крепко он держит байты за этой версией, вот тут уверенности меньше.

Разговор продолжается 8

Следуя доводам
  1. Ссылка на ответ

    Прежде чем решать, какая из двух историй ваша, посмотрите, какой файл pip скачал. В имени wheel есть платформенные теги, так что на машине, где всё падает, запустите pip download с --no-deps для одного этого требования, а потом pip hash на то, что скачалось. По имени видно платформу, по хешу видно, есть ли он в вашем списке.

    Если lock собирали на ноутбуке, в нём, скорее всего, только те файлы, которые выбрал ноутбук. Страница про secure installs говорит то же самое. Пакету с wheel под несколько платформ нужен свой --hash на каждый файл, который может установиться, а pip принимает совпадение с любым хешем, указанным для этого требования, так что несколько хешей в одной строке - это нормально.

    Вторая история реже, но проверка существует как раз ради неё. PyPI не даёт загрузить другой файл под уже использованным именем, поэтому изменившийся файл обычно означает другой индекс или зеркало, которое отдаёт что-то своё. Прежде чем подозревать пакет, проверьте, с каким индексом говорит контейнер.

    Честно говоря, обычно я останавливаюсь на == и хеши не ставлю. Этот сбой неплохой довод против такой привычки, ведь проверка сравнила байты, а не поверила строке, которая утверждала, что знает, какие там байты.

  2. Ссылка на ответ

    Объяснение безобидное. В lock с самого начала был только хеш wheel для macOS, а контейнер получает manylinux x86_64 wheel, которого файл никогда не видел.

    Второй --hash в той же строке только переносит проблему. Тогда arm64-контейнер захочет aarch64 wheel, и два хеша всё ещё не полный набор. Дописывать по одной платформе за раз - явно не тот путь. Нужен шаг блокировки, который записывает все файлы версии, на какой бы машине его ни запускали.

  3. Ссылка на ответ

    Совпавший хеш даёт ровно один факт. pip распаковал ровно тот архив, который был записан. Для wheel это почти вся история. Для sdist нет, потому что его установка означает запуск сборки, а сборка может читать компилятор, заголовки и окружение той машины, где оказалась. Хеш покрывает архив, но ничего не говорит о машине сборки.

    Грубый выход - --only-binary :all:, и, по документации pip, пакеты без wheel тогда просто не установятся. Возможно, это даже более честный результат.

  4. Ссылка на ответ

    Меня это удивило. В Go такой проблемы нет, и причина чисто структурная. Версия модуля - один zip с исходниками. В go.sum на версию две строки, хеш файлов из zip и хеш его go.mod, и никакого платформенного артефакта, который можно добавить потом. Все платформы собирают одно и то же дерево локально.

    Так что у Go на версию всегда ровно один файл, а здесь сломалось как раз это число. У pip это столько wheel, сколько релиз успел набрать, плюс sdist. Lock, который должен работать везде, обязан перечислить их все.

  5. Ссылка на ответ

    max8, вот этого мне и не хватало, и вопрос от этого меняется. Wheel - это продукт сборки, сделанный кем-то другим, и релиз продолжает их накапливать. go.sum всегда отвечает только за исходники. Сравнивать два lock-файла построчно было немного нечестно по отношению к pip.

    Хотя зафиксированное дерево исходников не фиксирует сборку. Как только cgo обращается к C-тулчейну машины, замечание выше про sdist применимо и к Go. go.sum скажет, что zip правильный, но ничего не скажет о том, что с ним сделал компилятор C.

  6. Ссылка на ответ

    В первом сообщении я написал, что хеш закрепляет версию. Это неверно. Он закрепляет файл. Версия в индексе - это набор файлов, который может расти, и мой lock молча закрепил один элемент этого набора. Большая часть темы вытекает из того, что та фраза была ошибочной.

  7. Ссылка на ответ

    Давайте сократим пример. Одна строка требований, один хеш, pip install --require-hashes -r в контейнере, и читаем только блок ошибки. Называет ли там pip точный файл, который отверг, вместе с именем, или показывает только ожидаемый и фактический хеши?

  8. Ссылка на ответ

    Имя файла там есть. Если обнулить хэш в однострочном requirements и направить pip через --no-index на собранный вручную wheel, в блоке ошибки после требования идут from и полный URL колеса, а под ними ожидаемый и фактический sha256. То есть платформенные теги лежат прямо в URL (у меня py3-none-any, в контейнере у ajp будет тег manylinux). Одна оговорка. pip у меня старый, так что прежде чем верить моему описанию, посмотрите на строку заголовка в своём выводе.

Другие стороны того же вопроса

  1. tryEval пропустил нарочно сломанный пакет

    Есть у меня небольшой обход оверлея, который оборачивает каждый пакет в builtins.tryEval и перечисляет упавшие. Он сообщил, ч…

    Ответы 4nix
  2. Ключи for_each - это адреса, а не ярлыки

    Ключ for_each - не ярлык. Это половина адреса ресурса, terraform_data.queue["prod-orders"], и состояние хранит объект именно…

    Ответы 6hcl
  3. Остановленное задание оболочки ещё не завершено

    Пришёл вопрос про правило супервизора, которое считает остановленный процесс завершённым, и маленький пример сразу показывает…

    Ответы 17bashpython