EnglishРусский Map

Установка Ruby-gem'ов через go get

title
Установка Ruby-gem'ов через go get
type
summary
summary
Несбитт направляет GOPATH на путь загрузки Ruby и получает журнал прозрачности, которого нет у RubyGems
tags
package-registry, go, ruby, supply-chain, dependencies
created
2026-07-23
updated
2026-07-23
lang
ru
source_updated
2026-07-23
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

Мысленный эксперимент Эндрю Несбитта: указать путь загрузки Ruby в GOPATH, положить go.mod в репозиторий gem'а и выполнить go get github.com/rack/rack@v3.0.0. Golang распаковывает модуль в $GOPATH/pkg/mod/github.com/rack/rack@v3.0.0/, вы направляете RUBYLIB на его директорию lib/, и require "rack" работает. Ruby всё равно, каким образом появились эти файлы.

Всё это работает лишь потому, что в Golang приняли три решения относительно именования, транспорта и контроля целостности, которые оказались независимыми от языка, и ни одно из них не заглядывает внутрь zip-архива.

Ничто не проверяет, что это Golang

Сервису proxy.golang.org нужен файл go.mod в корне репозитория, однако версии берутся из git-тегов, а сам go.mod вовсе не обязан описывать валидный код на Golang. sum.golang.org вычисляет хеш любого полученного zip-архива; он не парсит Golang, не проверяет компиляцию пакетов и вообще ни о чём не беспокоится. Этим уже пользуются: protobuf-определения, схемы JSON, модули Terraform и обычные файлы данных распространяются как модули Golang, не содержащие никакого Golang. Ratatoskr использует похожий трюк, раздавая собственную идентификацию модулей через mesh-сеть (см. ratatoskr).

Следствие, на которое на самом деле указывает Несбитт: ваш Ruby-gem теперь наследует свойства цепочки поставок Golang. Каждое скачивание проходит через кэширующий прокси, SHA-256 zip-архива модуля навсегда записывается в журнал прозрачности на базе дерева Меркла, побеждает первое скачивание, а мейнтейнер, решивший позже переписать тег, получит несовпадение хешей, из-за которого любой клиент Golang выдаст ошибку. У RubyGems нет журнала прозрачности. У sum.golang.org он есть. Притворившись модулями Golang, вы получили более надёжную проверку целостности для своих gem'ов и попутно обошли rubygems.org.

Самоописывающиеся пути против центрального индекса

import "github.com/foo/bar" несёт в себе хост, организацию и репозиторий. Ничто не резолвится через индекс. gem install foo - или npm install lodash, или pip install requests - это магическая строка, которая ничего не значит без владеющего ею реестра. Центральные индексы дают короткие имена и место для подачи жалоб на нарушения; взамен они требуют управления, модерации и создают единую точку отказа. Пути на основе доменов устойчивы к сквоттингу, поскольку для сквоттинга нужно владеть доменом, но они настолько многословны, что никто не хочет писать require "github.com/psf/requests" в начале каждого файла.

Deno пытался сделать это с импортами по URL и отступил к JSR - обычному реестру с короткими именами. Компромиссы вполне ощутимы, и большинство сообществ уже решило, что они того не стоят: инструментарий работал бы, а вот миграция нет, ведь переделка означает изменение того, как все пишут импорты.

Что нужно, чтобы стать Bundler'ом

В основном рекурсия. Распарсить gemspec, найти зависимости, выполнить для них go get, повторить, записать go.sum как lock-файл. Граф зависимостей здесь тот же самый, что строит Bundler; изменилась только доставка. Proof of concept лежит в andrew/go-bundler.

Отсюда вытекает одно структурное отличие: Golang использует Minimal Version Selection. Запросите v1.2.0, и вы получите v1.2.0, а не самую свежую версию, удовлетворяющую ограничению, что делает go.sum чем-то почти второстепенным. Bundler и большинство пакетных менеджеров предпочитают брать самое свежее совпадение, и именно поэтому Gemfile.lock критически важен - без него вы получите всё самое последнее на текущий момент. MVS меняет принцип "всегда самое свежее" на "скучно и предсказуемо", не требуя SAT-решателей или поиска с возвратом (backtracking). См. default-version-bound-constraints по смежному вопросу о том, что пакетный менеджер по умолчанию записывает в ваш манифест.

Ломаются две вещи. Golang приводит заглавные буквы в путях к нижнему регистру (case folding), так как macOS и Windows считают A и a одним и тем же файлом, поэтому BurntSushi/toml попадает на диск как !burnt!sushi/toml, и любой Ruby-инструментарий поверх этого наследует такое поведение. Вторая проблема - нативные расширения: gem'ам, вызывающим make для сборки C-кода, просто негде это делать, поскольку Golang ожидает исходный код или предсобранные бинарники. С gem'ами на чистом Ruby всё в порядке.

Суть шутки

Несбитт рассматривает это как часть более крупной декомпозиции: пакетный менеджер - это не единая система, а шесть соединённых вместе компонентов: именование, обнаружение, разрешение зависимостей, транспорт, контроль целостности и установка. Необычные решения Golang пришлись как раз на те три из них, которые не имеют никакого отношения к языку, благодаря чему и стал возможен этот трюк с Ruby. Если бы у каждой экосистемы был общий слой дистрибуции с адресацией по содержимому, журналом прозрачности и глобальным кэшированием, никому не пришлось бы притворяться модулем Golang. Вместо этого Google построил анонимный CDN для пакетов с минимальным управлением и разрешил бесплатно пользоваться им кому угодно, так что теперь там хранится неизвестное количество файлов .rb.

Связанные страницы

Тот же автор, что и у features-to-steal-from-npmx, и тот же интерес к устройству реестров пакетов. Аргумент о журнале прозрачности связан с темами supply-chain-security и reproducible-builds: проверяемый хеш ценен лишь настолько, насколько вы способны воспроизвести артефакт, на который он указывает. dependency-vendoring предлагает противоположный ответ на то же беспокойство: не доверяйте слою дистрибуции, коммитьте код в репозиторий.