Итак, вы хотите написать менеджер пакетов

Вы проснулись сегодня утром, скатились с кровати и подумали: «Знаешь что? У меня в жизни недостаточно несчастий и страданий. Я знаю, что делать - напишу менеджер языковых пакетов! »

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

Теперь у меня также есть скрытый мотив: прямо сейчас сообществу Go действительно нужно правильное управление пакетами, и я участвую в подходе. Поэтому я буду часто возвращаться к Go в качестве основного примера, а в конце есть специальный раздел, посвященный Go. Но в действительности основное внимание здесь уделяется общим принципам проектирования управления пакетами и ограничениям предметной области, а также тому, как они могут применяться на разных языках.

Управление пакетами ужасно, вы должны выйти прямо сейчас

Управление пакетами - неприятная область. Действительно противно. На первый взгляд это кажется чисто технической проблемой, поддающейся чисто техническим решениям. И поэтому вполне разумно люди так подходят к этому. Со временем эти люди неумолимо приходят к выводу, что:

  1. программное обеспечение ужасное
  2. люди ужасные
  3. слишком много разных сценариев
  4. ничего точно не будет работать
  5. доказано, что ничего точно не будет работать
  6. наша жизнь - бессмысленные возмущения в вихре хаоса и энтропии

Если вы когда-либо создавали… ну, практически любое программное обеспечение, то этот эпифанический прогресс, вероятно, кажется вам знакомым. Это спираль смерти дизайна, которая, как подсказывает нам опыт, является сигналом для того, чтобы вернуться назад и пересмотреть ожидания и предположения. Достаточно часто оказывается, что вы просто изначально неправильно сформулировали проблему. И - счастливого дня! - вот и все: те, кто рассматривает управление пакетами как чисто техническую проблему, неизбежно, даже подсознательно, ищут что-то, что может полностью и правильно автоматизировать обновления.

Прекрати это. Пожалуйста. Вам будет плохо.

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

Но подождите! Я уже забегаю вперед.

LOLWUT - это «менеджер пакетов»

Вы знаете, как работает Интернет, поэтому вы, вероятно, уже прочитали первые два предложения Википедии. Отлично, теперь вы эксперт. Итак, вы знаете, что сначала мы должны решить, какой вид менеджера пакетов написать, потому что в противном случае это похоже на "Эй, передай мне этот поклон?" Вы говорите Конечно, а затем передаете мне стрелок, но я хотел ленту, и Соня пошла за своей виолончелью. Вот меню:

  • Диспетчер пакетов ОС / системы (SPM): мы здесь не поэтому
  • Диспетчер языковых пакетов (LPM): интерактивный инструмент (например, `go get`), который может извлекать и создавать определенные пакеты исходного кода для определенного языка. Плохие выгружают полученный исходный код в глобальный неверсионный пул (GOPATH), а затем маниакально хохочут над вашей пылкой надеждой на то, что совокупное состояние этого пула имеет логичный смысл.
  • Диспетчер зависимостей проекта / приложения (PDM): интерактивная система для управления зависимостями исходного кода отдельного проекта на определенном языке. Это означает указание, извлечение, обновление, размещение на диске и удаление наборов зависимого исходного кода таким образом, чтобы коллективная согласованность сохранялась после завершения любой отдельной команды . Его вывод - который точно воспроизводится - представляет собой автономное дерево исходных текстов, которое действует как ввод для компилятора или интерпретатора. Вы можете думать об этом как о компиляторе, нулевой этап.

Основное различие здесь заключается между системами, которые помогают разработчикам создавать новое программное обеспечение, и системами для пользователей, которые устанавливают экземпляр некоторого программного обеспечения. SPM - это системы для пользователей, PDM - это системы для разработчиков, а LPM часто и то и другое. Но когда LPM не поддерживаются PDM, счастливый гибрид превращается в сварливую химеру.

С другой стороны, PDM вполне удовлетворяют без LPM, хотя на практике обычно имеет смысл объединить их вместе. Однако такое объединение часто сбивает зрителей с толку, когда они объединяют LPM и PDM или, что еще хуже, полностью игнорируют последние.

Не делай этого. Пожалуйста. Вам будет плохо.

PDM в значительной степени находятся внизу этого стека. Поскольку они сочиняют партии более высокого уровня (и потому, что это то, в чем сейчас отчаянно нуждается Го), мы собираемся сосредоточиться на них. К счастью, описать их обязанности довольно просто. Инструмент должен правильно, легко и быстро пройти следующие этапы:

  1. Боже мой, из бесчисленного множества возможных форм и беспорядка вокруг реального программного обеспечения в разработке, набор непосредственных зависимостей, на которые разработчик намеревается полагаться, затем
  2. преобразовать это намерение в точный, рекурсивно исследуемый список зависимостей исходного кода, чтобы любой - разработчик, другой разработчик, система сборки, пользователь - мог
  3. создать / воспроизвести дерево источников зависимостей из этого списка, тем самым
  4. создание изолированного, автономного артефакта проекта + зависимостей, который может быть введен в компилятор / интерпретатор.

Помните, что управление пакетами касается не только компьютеров, но и людей? Это отражается в словах «намереваться» и «воспроизводить» соответственно. Существует естественное противоречие между потребностью в абсолютной алгоритмической определенности результатов и плавностью, присущей разработке, осуществляемой людьми. Это внутреннее и неизбежное напряжение требует разрешения. Обеспечение этого разрешения - это принципиально то, что делают PDM.

Мы встретили врага, и это мы

PDM усложняет не алгоритмическая сторона. Их конечные результаты - это нулевая фаза компилятора или интерпретатора, и хотя специфика этого варьируется от языка к языку, каждый по-прежнему представляет собой четко определенную цель. Как и в случае со многими другими проблемами в вычислениях, задача состоит в том, чтобы выяснить, как представить эти машинные требования таким образом, чтобы они соответствовали человеческим ментальным моделям.

Теперь мы говорим о PDM, что означает, что мы говорим о взаимодействии с миром FLOSS - извлечении из него кода и, возможно, публикации кода обратно в него. (По правде говоря, не только FLOSS - экосистемы кода внутри компаний часто представляют собой микрокосмос одного и того же. Но давайте назовем все это сокращенно FLOSS.) Я бы сказал, что основная форма этой сплетенной с FLOSS ментальной модели выглядит примерно так:

  • У меня есть некое программное обеспечение, которое я создаю или обновляю - «проект». Когда я работаю над этим проектом, он становится моим центром, моим домом в мире FLOSS, и якоря всех остальных соображений, которые следуют далее.
  • У меня есть представление о том, что нужно сделать в моем проекте, но я должен предположить, что мое понимание в лучшем случае неполное.
  • Я знаю, что я / моя команда несу окончательную ответственность за то, чтобы создаваемый нами проект работал так, как задумано, независимо от безумия, которое неизбежно происходит на начальном этапе.
  • Я знаю, что если я попытаюсь написать весь код сам, это займет больше времени и, вероятно, будет менее надежным, чем использование закаленных в боях библиотек.
  • Я знаю, что полагаться на код других людей означает привязать мой проект к их проекту, что влечет за собой, по крайней мере, некоторые логистические и когнитивные издержки.
  • Я знаю, что существует предел, за которым я не могу разобрать весь код, который я набираю.
  • Я не знаю всего программного обеспечения, которое существует, но я знаю, что есть намного, намного, чем то, о чем я знаю. Некоторые из них могут иметь отношение к делу, почти все - нет, но поиск и оценка потребуют времени.
  • Я должен подготовиться к тому, что большая часть того, что там происходит, может оказаться чушью - или, по крайней мере, Я так и испытаю. Также потребуется время, чтобы отделить пшеницу от плевел и мои собственные ощущения от фактов.

Темы здесь - время, риск и неопределенность. При разработке программного обеспечения во всех направлениях есть неизвестные; Из-за нехватки времени вы не можете исследовать все, а изучение неправильного предмета может навредить вашему проекту или даже погубить его. Некоторые неопределенности могут быть увеличены или уменьшены в некоторых проектах, но мы не можем заставить их исчезнуть. Всегда. Это естественные ограничения. Фактически, некоторые из них даже необходимы для функционирования открытых программных экосистем, настолько, что мы их кодифицируем:

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ КАК ЕСТЬ, БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ​​ГАРАНТИЯМИ КОММЕРЧЕСКОЙ ЦЕННОСТИ, ПРИГОДНОСТЬ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ
- MIT Лицензия (выделено мной)

И вы знаете, что что-то серьезно, когда это на стандартном юридическом языке, который все используют, но никто не читает.

Хорошо, хорошо, я понял: создание программного обеспечения полно потенциально опасных неизвестностей. Однако мы не собираемся прекращать это делать, поэтому возникает вопрос: как мы можем быть там, писать программное обеспечение / скакать в джунглях неопределенности, но одновременно оградить наши проекты (и здравомыслие) от всех этих рисков?

Мне нравится думать о решении, используя аналогию из общественного здравоохранения: снижение вреда. Некоторое время назад Джулия Эванс написала прекрасную статью под названием Снижение вреда для разработчиков. Он короткий и стоит того, чтобы его прочесть, но я перефразирую суть:

Люди собираются заниматься рискованными делами. Вместо того, чтобы говорить, что ВЫ НЕПРАВИЛЬНО ДЕЛАЕТЕ ТО, ЧТО ТОЛЬКО НЕ ДЕЛАЙТЕ ЭТОГО, мы можем помочь сделать эти действия менее рискованными.

Это менталитет, который мы должны принять при создании практического инструмента для разработчиков. Не потому, что мы цинично угождаем карикатуре ковбоя (ковбоя? Ковбоев?) С наименьшим общим знаменателем, а потому что ... ну, посмотрите на эту башню неуверенности! Риск - это необходимая часть разработки. Вариант использования PDM предназначен для инструмента, который побуждает разработчиков снижать неопределенность путем диких экспериментов, одновременно сохраняя проект как можно более стабильным и разумным. Как ракета с якорем! Или что-то.

При других обстоятельствах, возможно, именно здесь я начинаю выкладывать варианты использования. Но… давай не будем. То есть, давайте не будем переходить к перечислению конкретных случаев, за которым неизбежно следует поиски в темноте удручающего, галочки в квадрате, компромисса между разработками комитетов. Во всяком случае, для PDM, я думаю, есть способ лучше. Вместо того чтобы подходить к решению с точки зрения вариантов использования, я возьму грубую заметку о распределенных системах и сформирую ее с точки зрения состояний и протоколов.

Состояния и протоколы

Есть веская причина сосредоточиться на состоянии. Понимание того, какое у нас состояние, а также когда, почему и как оно изменяется, - это заведомо хороший способ стабилизировать программное обеспечение от этого вихря хаоса и энтропии. Если мы сможем установить хотя бы некоторые общие правила о том, что государства должны представлять и протоколы, с помощью которых они взаимодействуют, это наведет порядок и направление в крысиное гнездо. (Затем приведите примеры использования.) Черт, даже эта статья прошла несколько изменений, прежде чем я понял, что она тоже должна быть структурирована в соответствии с состояниями и протоколами.

Познакомьтесь с актерами

PDM постоянно ставят танец между четырьмя отдельными состояниями на диске. Это, по-разному, входы и выходы различных команд PDM:

  • Код проекта: активно разрабатываемый исходный код, для которого мы хотим, чтобы PDM управлял своими зависимостями. Поскольку сейчас не 1967 год, весь код проекта находится под контролем версий. Для большинства PDM код проекта - это весь код в репозитории, хотя это может быть просто подкаталог.
  • Файл манифеста: созданный человеком файл (обычно с помощью компьютера), в котором перечислены зависящие от него пакеты, которыми нужно управлять. Обычно манифест также содержит другие метаданные проекта или инструкции для LPM, с которыми PDM может быть связан. (Это должно быть совершено, иначе ничего не сработает.)
  • Файл блокировки: машинный файл со всей информацией, необходимой для [повторного] создания полного дерева источников зависимостей. Создается путем транзитивного преобразования всех зависимостей из манифеста в конкретные неизменяемые версии. (Это всегда должно быть зафиксировано. Возможно. Подробности позже.)
  • Код зависимости: весь исходный код, указанный в файле блокировки, расположен на диске таким образом, чтобы компилятор / интерпретатор мог найти и использовать его по назначению, но изолирован, чтобы у других не было причин для изменения. Это. Также включает любую дополнительную логику, которая может потребоваться в некоторых средах, например, автозагрузчики. (Это не обязательно.)

Давайте проясним - есть PDM, в которых всего этого нет. У некоторых они могут быть, но представляют их по-разному. У большинства новых сейчас есть все, но это не область, где есть полный консенсус. Я, однако, утверждаю, что каждой ДПМ нужна какая-то версия этих четырех состояний для достижения полного объема ответственности, и что такое разделение ответственности является оптимальным.

Трубопроводы внутри трубопроводов

Штаты - это только половина картины. Другая половина - это протоколы - процедуры передвижения между штатами. К счастью, это довольно просто.

Состояния образуют, грубо говоря, конвейер, где входными данными для каждого этапа являются состояния его предшественника. (В основном - этот первый скачок беспорядочный.) Трудно переоценить положительное влияние этой линейности на разумность реализации: среди прочего, это означает, что всегда существует четкое «прямое» направление для логики PDM, что устраняет значительную двусмысленность. Меньшая двусмысленность, в свою очередь, означает меньшее количество инструкций от пользователя для правильной работы, что делает использование инструмента проще и безопаснее.

PDM также существуют в некоторых более крупных трубопроводах. Первый - это компилятор (или интерпретатор), поэтому я ранее называл их «нулевой фазой компилятора»:

Для языков, скомпилированных заранее, PDM являются своего рода препроцессором: их совокупный результат - это основной код проекта плюс код зависимостей, организованный таким образом, что когда препроцессор (или его эквивалент) видит включаемый код X »в источнике, X будет соответствовать задумке автора проекта.

Идея «нулевой фазы» все еще сохраняется в динамическом или JIT-компилируемом языке, хотя механика немного отличается. Помимо размещения кода на диске, PDM обычно необходимо переопределить или перехватить механизм загрузки кода интерпретатора, чтобы правильно разрешить включает. В этом смысле PDM создает для себя макет файловой системы, что, по мнению придирчивого, означает, что стрелка становится самореферентной. Что действительно важно, так это то, что от PDM ожидается размещение файловой системы до запуска интерпретатора. Итак, все еще «фаза ноль».

Другой конвейер, в котором существуют PDM, - это контроль версий. Это полностью ортогонально конвейеру компилятора. Типа, на самом деле ортогональный:

Видеть? PDM передает код компилятора по оси X, а система управления версиями обеспечивает хронологию по оси Y. Ортогонально!

…ждать. Вещи по оси X, время по оси Y ... это звучит странно похоже на евклидово пространство, которое мы используем для описания пространства-времени. Означает ли это, что PDM являются своего рода причудливой функцией топологического многообразия? А выходные данные компилятора - это буквально континуум пространства-времени !? Может быть! Я не знаю. Я плохо разбираюсь в математике. В любом случае у нас есть пространство и время, поэтому я называю это: каждое хранилище - это своя маленькая вселенная.

Какой бы глупой ни была эта метафора, она остается до странности полезной. Каждая репо-версия проекта наполнена своими собственными логическими правилами, которые развиваются в соответствии со своими независимыми временными рамками. И хотя я не знаю, насколько сложно будет выровнять настоящие вселенные разумным, симбиотическим способом, «довольно сложно» кажется безопасным предположением. Поэтому, когда я говорю, что PDM - это инструменты для выравнивания кодовых вселенных, я предполагаю, что это чертовски сложно. Недостаточно просто выровнять код один раз; вы также должны выровнять временные рамки. Только тогда вселенные могут расти и изменяться вместе.

«Выравнивание вселенных» пригодится позже. Но есть еще одна вещь, которую следует немедленно отметить в самом конвейере PDM. Хотя PDM действительно имеют дело с четырьмя физически дискретными состояниями, на самом деле работают только две концепции:

Взятые вместе, некоторая комбинация манифеста и кода проекта является выражением намерений пользователя. Это полезное сокращение отчасти из-за того, что в нем говорится о том, чего НЕ делать: вы не вмешиваетесь вручную с файлом блокировки или зависимостями по той же причине, что вы не меняете сгенерированный код. И, наоборот, PDM не должен ничего менять слева при создании файла блокировки или дерева зависимостей. Люди слева, машины справа.

Но это подозрительно красиво и аккуратно. Давайте немного честности:

Для многих программ «беспорядок» может быть даже благотворительным. И это может расстраивать, приводя к желанию удалить / не создавать возможности в инструментах, которые приводят к беспорядку или, по крайней мере, поощряют его. Однако помните о цели: снижение вреда. Задача PDM - не препятствовать разработчикам идти на риск, а делать это рискованное поведение максимально безопасным и структурированным. Риск часто приводит к беспорядку, по крайней мере, на мгновение. Такова природа нашего предприятия. Прими это.

Вероятно, самый простой способ ошибиться с PDM - это слишком много внимания уделять одной стороне в ущерб другой. Баланс должен быть сохранен. Слишком много справа, и вы получите неуклюжий беспорядок, вроде подмодулей git, которые разработчики просто не будут использовать. Слишком много слева, и производительность возрастет от того, что все будет "так просто!" рано или поздно (в большинстве случаев раньше) будут вытеснены огромным техническим долгом, возникшим в результате строительства на нестабильном основании.

Не делай тоже. Пожалуйста. Всем будет плохо.

Проявить манифест

Манифесты часто содержат много информации, зависящей от языка, и переход от кода к самовыражению, как правило, также зависит от языка. Было бы правильнее назвать это «Мир -› Манифест », учитывая количество возможных входов. Чтобы сделать это конкретным, давайте посмотрим на настоящий манифест:

Поскольку для этого не существует стандарта Go, я использую манифест Cargo (Rust). Cargo, как и многие другие программы этого класса, - это гораздо больше, чем просто PDM; это также типичная точка входа пользователя для компиляции программ на Rust. Манифест содержит метаданные о самом проекте в разделе пакет, параметры параметризации в разделе функции (и другие разделы) и информацию о зависимостях в разделе различных зависимостей. разделы. Мы исследуем каждый.

Центральный реестр пакетов

Первая группа в основном предназначена для центрального реестра пакетов Rust, crates.io. Вопрос о том, использовать ли реестр или нет, может быть самым важным вопросом, на который нужно ответить. Большинство языков с любым типом PDM имеют один: Ruby, Clojure, Dart, js / node, js / browser, PHP, Python, Erlang, Haskell, и т. д. Конечно, у большинства языков - Go является исключением - нет выбора, поскольку непосредственно в исходном коде недостаточно информации для извлечения пакетов. Некоторые PDM полагаются исключительно на центральную систему, в то время как другие также позволяют прямое взаимодействие с исходными репозиториями (например, на GitHub, Bitbucket и т. Д.). Создание, размещение и поддержка реестра - это непростая задача. И, кроме того, это… не моя проблема! Это статья не о создании высокодоступных сервисов хранения данных с высоким трафиком. Так что я их пропускаю. Повеселись!

… Кхм.

Что ж, я пропускаю как создать реестр, но не их функциональную роль. Действуя как кеши метаданных пакетов, они могут предложить значительные преимущества в производительности. Поскольку метаданные пакета обычно хранятся в манифесте, а манифест обязательно находится в исходном репозитории, проверка метаданных обычно требует клонирования репозитория. Однако реестр может извлекать и кэшировать эти метаданные и делать их доступными с помощью простых вызовов HTTP API. Намного, намного быстрее.

Реестры также могут использоваться для наложения ограничений. Например, в приведенном выше грузовом манифесте есть поле package.version:

[package]
version = "0.2.6"

Подумайте об этом немного. Ага: это безумие! Версии должны относиться к одной ревизии, чтобы их можно было использовать. Но, записывая его в манифест, этот номер версии просто скользит вместе с каждой сделанной фиксацией, тем самым применяя к нескольким ревизиям.

Cargo решает эту проблему, налагая ограничения в самом реестре: публикация версии в crates.io является абсолютно постоянной, поэтому не имеет значения, к каким другим коммитам может применяться эта версия. С точки зрения ящиков, это действительно доступно только первой ревизии.

Другие виды применения ограничений могут включать в себя проверку правильно сформированного пакета исходного кода. Если язык не диктует конкретную исходную структуру, тогда PDM может наложить ее, а реестр может обеспечить ее выполнение: npm может искать правильно сформированный объект модуля, или композитор может попытаться проверить соответствие автозагрузчику PSR. . (AFAIK, ни я, ни я даже не уверен, что это возможно). На таком языке, как Go, где код в значительной степени самоописывается, в подобных вещах практически нет необходимости.

Параметризация

Вторая группа - это параметризация. Детали здесь специфичны для Rust, но общая идея - нет: параметры - это четко определенные способы, с помощью которых можно изменить форму вывода проекта или его фактическое логическое поведение. Хотя некоторые аспекты могут пересекаться с обязанностями PDM, это часто выходит за рамки PDM. Например, целевые профили Rust позволяют контролировать уровень оптимизации, передаваемый его компилятору. А аргументы компилятора? ДПМ плевать.

Однако некоторые типы параметров - функции Rust, теги сборки Go, любое понятие профилей сборки (например, test vs. dev vs. prod) - может изменить используемые пути в логике проекта. Такие изменения могут, в свою очередь, сделать некоторые новые зависимости необходимыми или устранить необходимость в других. Это проблема PDM.

Если этот тип конфигурируемости важен в вашем языковом контексте, тогда ваша концепция «зависимости», возможно, должна допускать условность. С другой стороны, обеспечение минимального набора зависимостей (в основном) сводится только к уменьшению пропускной способности сети. Это позволяет оптимизировать производительность и, следовательно, пропускать. Мы вернемся к этому позже в разделе файлов блокировки.

Если вы не опытный Rustacean или менеджер пакетов, особенности Cargo могут вызвать некоторое недоумение: кто и когда принимает решение об использовании этих функций? Ответ: проект верхнего уровня, в собственном манифесте. Это подчеркивает важный момент, который я еще не затронул: проекты как приложения и проекты как библиотеки. Обычно приложения - это проект на вершине цепочки зависимостей - проект верхнего уровня, тогда как библиотеки обязательно находятся где-то внизу.

Однако различие между приложением и библиотекой не столь уж однозначно. На самом деле, это в основном ситуационный характер. Хотя библиотеки обычно могут находиться где-то в цепочке зависимостей, при выполнении тестов или тестов они являются проектом верхнего уровня. И наоборот, хотя приложения обычно находятся наверху, у них могут быть подкомпоненты, которые можно использовать в качестве библиотек, и, таким образом, они могут отображаться в нижней части цепочки. Лучше подумать об этом различии: «проект производит 0..N исполняемых файлов».

На самом деле приложения и библиотеки - это одно и то же - это хорошо, потому что это предполагает, что уместно использовать одну и ту же структуру манифеста как для приложений, так и для библиотек. Он также подчеркивает, что манифесты обязательно обращены как «вниз» (определяя зависимости), так и «вверх» (предлагая информацию и варианты выбора для иждивенцев).

Зависимости

Собственный домен PDM!

Каждая зависимость состоит как минимум из идентификатора и спецификатора версии. Также могут присутствовать параметризация или типы источников (например, необработанная VCS или реестр). Изменения в этой части манифеста обязательно связаны с одним из следующих:

  • Добавление или удаление зависимости
  • Изменение желаемой версии существующей зависимости
  • Изменения параметров или исходных типов зависимости

Это те задачи, которые на самом деле должны выполнять разработчики. Удаление настолько тривиально, что многие PDM не предоставляют команду, а просто ожидают, что вы удалите соответствующую строку из манифеста. (Я думаю, что команда rm того стоит, по причинам, о которых я расскажу позже.) Добавление, как правило, не должно быть сложнее, чем:

<tool> add <identifier>@<version>

или просто

<tool> add <identifier>

неявно запрашивать самую последнюю версию.

Единственное жесткое требование к идентификаторам - это то, что все они существуют в одном пространстве имен, и что PDM может собрать достаточно информации путем их анализа (возможно, с некоторой помощью из поля типа - например, git или bzr, vs. pkg, если он находится в центральном реестре), чтобы определить, как получить ресурс. По сути, это URI для конкретного домена. Просто убедитесь, что вы избегаете двусмысленности в схеме именования, и в основном у вас все хорошо.

Если возможно, вы должны попытаться убедиться, что идентификаторы пакетов совпадают с именами, используемыми для ссылки на них в коде (то есть с помощью операторов include), так как это на одну мысленную карту меньше для пользователей и на одну карту памяти меньше для статических анализаторов. . В то же время одна полезная вещь, которую PDM часто делают, если позволяют языковые ограничения, - это наложение одного пакета на другой. В мире ожидания, пока патчи будут приняты апстримом, это может быть удобным временным приемом для замены исправленного форка.

Конечно, вы также можете просто поменять вилку по-настоящему. Так почему вообще псевдоним?

Это восходит к духу манифеста: они - место, где хранятся намерения пользователя. Используя псевдоним, автор может сигнализировать другим людям - особенно сотрудникам проекта, но также и пользователям, - что это временный взлом, и они должны соответствующим образом сформулировать свои ожидания. А если неявно неясно, они всегда могут добавить комментарий, объясняя, почему!

Однако вне псевдонимов идентификаторы довольно банальны. Версии, однако, совсем не такие.

Погодите, нам нужно поговорить о версиях

Версии тяжелые. Может быть, это очевидно, а может, и нет, но понимание того, что делает их трудными (и проблему, которую они решают), имеет решающее значение.

Версии точно, недвусмысленно и недвусмысленно к черту всех связаны с кодом, который они украшают. Практически нет вопросов о реальной логике, на которые может дать окончательный ответ номер версии. В этом списке, на который никто не ответит, стоит один из самых важных вопросов работающего жесткого разработчика: «Будет ли мой код работать, если я перейду на другую версию?»

Несмотря на эти ограничения, мы используем версии. Широко. Причина проста в том, что одна из составляющих мировоззрения FLOSS:

Я знаю, что есть предел, за которым я не могу разобрать весь код, который втягиваю.

Если бы нам пришлось полностью изучить код перед его использованием или обновлением, мы бы никогда ничего не добились. Конечно, это может быть A Software Engineering Best Practice ™, но ее применение приведет к остановке индустрии программного обеспечения. Это гораздо больший риск, чем то, что какое-то программное обеспечение иногда не работает из-за того, что некоторые люди использовали неправильные версии.

Однако есть менее очевидная причина, по которой мы полагаемся на версии: механической альтернативы нет. То есть, согласно нашим текущим знаниям о том, как работают вычисления, универсальный инструмент (даже зависящий от языка), способный выяснить, будут ли комбинации кода работать так, как задумано, не может существовать. Конечно, вы можете писать тесты, но давайте не будем забывать о Дейкстре:

«Эмпирическое тестирование может доказать только наличие ошибок, но не их отсутствие».

Кто-то лучше меня в математике, информатике и теории типов, вероятно, смог бы объяснить это должным образом. Но на данный момент вы застряли со мной, поэтому вот мое беглое резюме: системы типов и проектирование по контракту могут иметь большое значение для определения совместимости, но если язык является полным по Тьюрингу, их никогда не будет достаточно. В лучшем случае они могут доказать, что код не несовместим. Попробуйте больше, и вы закончите рекурсивный спуск через новые крайние случаи, которые продолжают появляться. Один друг однажды назвал такие попытки игрой в Гёделевский удар крота. (Совет профессионала: не играйте. Победная серия Гёделя длится 85 лет.)

Это не (просто) заумная теория. Это подтверждает простую интуицию: «мой код правильно работает с вашим?» решение, люди должны быть вовлечены. Машины могут помочь, потенциально довольно сильно, выполняя часть работы и сообщая о результатах, но они не могут принять точное окончательное решение. Именно поэтому версии должны существовать, и почему системы вокруг них работают именно так: чтобы помогать людям принимать эти решения.

Единственная raison d’etre версий - это грубая сигнальная система от авторов кода к пользователям. Вы также можете думать о них как о системе курирования. При присоединении к системе типа SemVer версии могут предлагать:

  • Эволюция программного обеспечения за счет четко определенного отношения упорядочения между любыми двумя версиями.
  • Общая готовность данной версии к общедоступному использованию (т.е. ‹1.0.0, предварительные версии, альфа / бета / rc)
  • Вероятность несовместимости различных классов между любой данной парой версий.
  • Неявно, что если есть версии, но вы используете ревизию без версии, у вас могут быть плохие времена.

Чтобы такая система, как semver, была эффективной на вашем языке, важно установить некоторые специфические для языка рекомендации относительно того, какие логические изменения соответствуют основным, второстепенным и изменениям на уровне исправлений. Rust опередил этого. Идти надо, и еще можно. Без них не только все будут просто чувствовать - также известное как давайте предложим каждому свой собственный, вероятно, мусорный подход, а затем займемся территорией и защитой, - но и нет общих ценностей, к которым можно было бы обратиться в очереди задач, когда автор увеличивает неверный уровень версии. И эти разговоры очень важны. Они не только исправляют сиюминутную ошибку, но и позволяют нам коллективно улучшать использование версий для общения.

Несмотря на отсутствие необходимой взаимосвязи между версиями и кодом, существует по крайней мере один способ, которым версии напрямую помогают обеспечить работу программного обеспечения: они привлекают внимание общественности к нескольким конкретным ревизиям кода. С таким сосредоточением внимания Закон Линуса предполагает, что ошибки будут искоренены и исправлены (и выпущены в последующих версиях патчей). Таким образом, кураторский эффект сосредоточения внимания на определенных версиях снижает системный риск, связанный с этими версиями. Это помогает с другой неопределенностью FLOSS:

Я должен подготовиться к тому, что большая часть того, что там есть, вероятно, окажется хренью. Также потребуется время, чтобы отделить пшеницу от плевел.

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

Версии без версии

Многие PDM также позволяют использовать другие спецификаторы версии, отличные от semver. Это не обязательно, но имеет важное применение. Следует отметить два других типа спецификаторов версии, оба из которых более или менее требуют, чтобы базовый тип источника был репозиторием VCS: указание ветви для «отслеживания» или указание неизменяемой ревизии.

Тип используемого спецификатора версии на самом деле является решением о том, как вы хотите относиться к этой вышестоящей библиотеке. То есть: какова ваша стратегия согласования их вселенной с вашей? Лично я вижу это примерно так:

Версии предоставляют мне красивую упорядоченную среду пакетов. Филиалы привязывают меня к чьей-то поездке, где этот «кто-то» может, а может и не подпрыгнуть на сиропе от кашля и подуть. Исправления полезны, когда авторы проекта, который вам нужно привлечь, предоставили так мало рекомендаций, что вам просто нужно крутить колесо и выбрать ревизию. Как только вы найдете тот, который работает, вы записываете эту версию в манифест как сигнал своей команде, что вы никогда не хотите снова менять.

Теперь любое из этих могло пойти правильно или неправильно. Возможно, эти приятные на вид пакеты переполнены криптовалютой, прикрытой АНБ. Может, тот чувак, тащащий меня на бугле, на самом деле обученный инструктор по автомобильной безопасности. Может, это дружелюбный демон за рулеткой?

Тем не менее, что важно в этих различных идентификаторах, так это то, как они определяют процесс обновления. Редакции не требуют процесса обновления без; ветки постоянно преследуют апстрим, и версии, особенно с помощью спецификаторов диапазона версий, позволяют вам управлять желаемым типом поездки… до тех пор, пока автор апстрима знает, как правильно применять semver.

На самом деле, это всего лишь расширение и исследование еще одного аспекта мировоззрения FLOSS:

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

Мы знаем, что использование кода других людей всегда сопряжено с риском и накладными расходами. Наличие нескольких четко определенных шаблонов, по крайней мере, сразу делает стратегию согласования очевидной. И мы заботимся об этом, потому что, опять же, манифесты являются выражением намерений пользователя: простой взгляд на спецификатор версии манифеста ясно показывает, как автор хочет относиться к стороннему коду. Это не может улучшить исходный код, но следование шаблону снижает когнитивную нагрузку. Если ваш PDM работает хорошо, это также облегчит логистические проблемы.

Единица обмена

До сих пор я беспечно игнорировал что-то важное: что такое - проект, пакет или зависимость? Конечно, это набор исходного кода, и да, он каким-то образом исходит из системы контроля версий. Но является ли весь репозиторий проектом или только его часть? Настоящий вопрос здесь: «Что такое единица обмена исходным кодом?»

Для языков, не имеющих особо значимого отношения исходного уровня к файловой системе, репозиторий может быть благословением, поскольку он обеспечивает естественную границу для кода, которой язык не имеет. В таких случаях репозиторий является единицей обмена, поэтому вполне естественно, что манифест находится в корне репозитория:

$ ls -a
.
..
.git
MANIFEST
<ur source code heeere>

Однако для языков, которые имеют четко определенную связь с файловой системой, репозиторий не имеет значения в качестве границы. (Go является наиболее ярким примером этого, который я знаю, и я рассмотрю его в разделе Go.) Фактически, если язык делает разумным независимый импорт различных подмножеств кода репозитория, то использование репозитория в качестве единицы обмен может действительно помешать.

Это может быть неудобно для потребителей , которым требуется только некоторое подмножество репозитория или разные подмножества в разных версиях. Или это может причинить боль автору, который чувствует, что должен разбить репозиторий на атомарные единицы для импорта. (Поддержание большого количества репозиториев - отстой; мы делаем это только потому, что последнее поколение DVCS убедило всех нас, что это хорошая идея.) В любом случае для таких языков может быть предпочтительнее определить единицу обмена другое, кроме репозитория. Если вы пойдете по этому пути, вам нужно помнить следующее:

  • Манифест (и файл блокировки) приобретают особенно значимую связь с соседним кодом. Обычно в манифесте определяется одна «единица».
  • По-прежнему АБСОЛЮТНО НЕОБХОДИМО, чтобы ваша единица обмена находилась на отдельной временной шкале, и вы больше не можете полагаться на VCS, чтобы предоставить ее. Ни шкалы времени, ни вселенных; ни вселенных, ни ДПМ; нет ДПМ, нет здравомыслия.
  • И помните: программное обеспечение достаточно сложно без добавления временного измерения. Информация о хронологии не должна содержаться в самом источнике. Никто не хочет писать настоящий код внутри тессеракта.

Между версиями в файле манифеста и зависимостями путей, похоже, Cargo это тоже вычислило.

другие мысли

В основном это небольшие новые идеи, но также и некоторый обзор.

  • Выбирайте формат в первую очередь для людей, во вторую очередь для машин: TOML или YAML, иначе (тьфу) JSON. Такие форматы декларативны и не имеют состояния, что упрощает работу. Правильные комментарии - это большой плюс - манифесты - это дом экспериментов, и оставление заметок для ваших соавторов о том, что и почему из указанных экспериментов может быть очень полезным!
  • TIMTOWTDI, по крайней мере, на уровне PDM, является вашим заклятым врагом. Полностью автоматизируйте ведение домашнего хозяйства. Если команды PDM, изменяющие манифест, выходят за рамки команд добавления / удаления и обновления, это, вероятно, случайно, а не существенно. Посмотрите, можно ли это выразить в терминах этих команд.
  • Решите, нужен ли центральный реестр пакетов (почти наверняка да). Если это так, поместите в манифест столько информации для реестра, сколько необходимо, при условии, что никоим образом не мешает и не сбивает с толку информацию о зависимостях, необходимую PDM.
  • Избегайте наличия в манифесте информации, которая может быть однозначно выведена из статического анализа. Одно из первых мест в списке головных болей, которые вы не хотите, - это неразрешимые разногласия между манифестом и кодовой базой. Сложно написать соответствующий статический анализатор? Жесткие тидди-винки. Разберитесь в этом, чтобы вашим пользователям не приходилось этого делать.
  • Решите, какую схему управления версиями использовать (возможно, semver или что-то в этом роде / добавление полного порядка). Вероятно, также разумно разрешить вещи за пределами базовой схемы: возможно, имена веток, может быть, неизменяемые идентификаторы фиксации.
  • Решите, будет ли ваше программное обеспечение сочетать поведение PDM с другими функциями, такими как LPM (возможно, да). Храните все необходимые для этой цели инструкции отдельно от того, что нужно PDM.
  • Существуют и другие типы ограничений, например, необходимая минимальная версия компилятора или интерпретатора, которые, возможно, имеет смысл включить в манифест. Хорошо. Просто помните, что они второстепенны по отношению к основной ответственности PDM (хотя в конечном итоге он может перемежаться с ней).
  • Определитесь с единицей обмена. Сделайте выбор, соответствующий семантике вашего языка, но обязательно убедитесь, что у всех ваших модулей есть свои временные шкалы.

Карантин

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

Эта трансформация - главный процесс, с помощью которого мы уменьшаем ущерб, связанный с неотъемлемыми рисками развития. Это также то, как мы решаем одну конкретную проблему в мировоззрении FLOSS:

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

Некоторые люди не видят ценности в точной воспроизводимости. Манифестов достаточно!, Неважно, пока проект не станет серьезным и npm стал популярным задолго до shrinkwrap (файл блокировки npm) был рядом! - вот некоторые аргументы, которые я видел. Но эти аргументы кажутся мне ошибочными. Вместо того, чтобы спрашивать: Нужны ли мне воспроизводимые сборки? спросите: "Я что-нибудь теряю от воспроизводимых сборок?" Буквально каждый в конечном итоге выигрывает от них. (Если только не рассылать сообщения о тарболах и SSH'ы подталкивать к внесению изменений в nano - это ваше развлечение). Вопрос только в том, нужна ли вам воспроизводимость а) сейчас, б) в ближайшее время или в) можем ли мы подружиться? потому что я думаю, что, возможно, вы на самом деле не участвуете в разработке программного обеспечения, но все же читаете это, что делает вас странным человеком, а мне нравятся странные люди.

Единственным реальным потенциальным недостатком воспроизводимых сборок является то, что инструмент становится дорогостоящим (медленным) или сложным (дополнительные служебные команды или непонятные параметры), что препятствует процессу разработки. Это реальная проблема, но это также аргумент против плохой реализации, а не сама воспроизводимость. Фактически, это действительно руководство по пользовательскому интерфейсу, предлагающее, как стройный выглядит в PDM: быстро, неявно и максимально автоматизировано.

Алгоритм

Что ж, эти правила просто кричат ​​«алгоритм!» И действительно, создание файла блокировки должно быть полностью автоматизировано. Сам алгоритм может быть довольно сложным, но основные шаги легко изложить:

  • Постройте граф зависимостей (так: направленный, ациклический и с разными пометками), рекурсивно следуя зависимостям, начиная с тех, которые перечислены в манифесте проекта.
  • Выберите ревизию, которая соответствует ограничениям, указанным в манифесте.
  • Если обнаружены какие-либо общие зависимости, согласовайте их с помощью ‹strategy›
  • Сериализуйте окончательный график (с любыми дополнительными метаданными для каждого пакета) и запишите его на диск. Дин-дин, у тебя есть файл блокировки!

(Примечание автора: общая проблема здесь - логическая выполнимость, которая является NP-полной. Эта разбивка по-прежнему примерно полезна, но упрощает алгоритм.)

Это предоставляет нам файл блокировки, содержащий полный список всех зависимостей (т.е. все достижимые зависимости в вычисленном, полностью разрешенном графе зависимостей). Все достижимо означает, что у нашего проекта есть три прямые зависимости, например:

Мы по-прежнему напрямую включаем все транзитивно достижимые зависимости в файл блокировки:

Точное количество необходимых метаданных зависит от специфики языка, но основными являются идентификатор пакета, адрес для извлечения и наиболее близкие к неизменяемой ревизии (т. Е. Хеш фиксации), допускаемые типом источника. Если вы добавляете что-нибудь еще - например, целевое расположение на диске - это должно быть только для гарантии того, что нет абсолютно никакой двусмысленности в том, как выгружать код зависимости.

Из четырех основных шагов алгоритма первый и последний более или менее просты, если вы знакомы с графиками. К сожалению, я не в силах передать увлеченность графами в статье; Пожалуйста, не стесняйтесь обращаться ко мне, если вы здесь застряли.

С другой стороны, два средних шага (которые на самом деле представляют собой просто разделение на две части «выберите ревизию») имеют проблемы, с которыми мы можем и должны бороться. Второе в основном простое. Если файл блокировки уже существует, сохраните указанные заблокированные версии, если:

  • Пользователь прямо указал игнорировать файл блокировки
  • Плавающая версия, как и ветка, является спецификатором версии.
  • Пользователь запрашивает обновление одной или нескольких зависимостей.
  • Манифест изменен, и больше не принимает их
  • Разрешение общей зависимости не позволит этого

Это помогает избежать ненужных изменений: если манифест допускает версии 1.0.7, 1.0.8 и 1.0.9, но ранее вы были заблокированы на 1.0.8, то при последующих разрешениях следует заметить это и повторно использовать 1.0.8. Если это кажется очевидным, хорошо! Это простой пример, иллюстрирующий фундаментальную взаимосвязь между манифестом и файлом блокировки.

Этот базовый идеальный подход хорошо известен - Бандлер называет его консервативным обновлением. Но его можно расширить. Некоторые PDM не рекомендуют или, по крайней мере, безразличны блокировать файлы, хранящиеся в библиотеках, но это упущенная возможность. Во-первых, это упрощает работу пользователей, удаляя условность - фиксировать файл блокировки всегда, несмотря ни на что. Но также при вычислении депграфа проекта верхнего уровня достаточно легко заставить PDM интерпретировать файл блокировки зависимости как ревизию предпочтения, а не требования. Предпочтения, выраженные в зависимостях, конечно, имеют меньший приоритет, чем те, которые выражены в файле блокировки проекта верхнего уровня (если таковой имеется). Как мы увидим дальше, при наличии общих зависимостей такие предпочтения могут способствовать еще большей стабильности сборки.

Бриллианты, семВер и медведи, о боже!

Третий вопрос сложнее. Мы должны выбрать стратегию выбора версии, когда два проекта имеют общую зависимость, и правильный выбор во многом зависит от языковых характеристик. Это также известно как «проблема алмазной зависимости» и начинается с подмножества нашего депграфа:

Если здесь не указаны версии, проблем нет. Однако, если для A и B требуются разные версии C, возникает конфликт, и ромб разделяется:

Здесь есть два класса решений: разрешить несколько C (дублирование) или попытаться разрешить конфликт (согласование). Некоторые языки, например Go, не поддерживают первое. Другие делают, но с различным уровнем рискованных побочных эффектов. Ни один из подходов по своей сути не превосходит правильность. Однако вмешательство пользователя никогда не требуется при использовании нескольких C, что делает этот подход намного проще для пользователей. Давайте сначала займемся этим.

Если язык допускает несколько экземпляров пакета, следующий вопрос - это состояние: если есть глобальное состояние, которым зависимости могут и обычно манипулируют, несколько экземпляров пакета могут быстро запутаться. Таким образом, в node.js, где нет тонны общего состояния, npm удалось избежать любой возможности конфликтов, просто намеренно проверив все в форме сломанного ромбовидного дерева. (Хотя он может достичь счастливого ромба с помощью дедупликации, которая используется по умолчанию в npm v3).

С другой стороны, в Javascript-интерфейсе есть DOM - прародитель глобального разделяемого состояния, что делает такой подход гораздо более рискованным. Это делает для беседки гораздо лучшую идею согласовать (сгладить, как они это называют) все зависимости, общие или нет. (Конечно, интерфейс javascript также остро нуждается в минимизации размера полезной нагрузки, отправляемой клиенту.)

Если ваш язык позволяет это и система типов не подавится этим, и риски глобального состояния незначительны, и вы неплохо относятся к раздуваемым двоичным файлам / процессам и (вероятно, незначительным) затратам на производительность во время выполнения, И инструменты затягивания статического анализа и транспиляции в порядке, тогда дублирование - это то, что нужно общее решение deps, которое вы ищете. Это много условий, но это все же может быть предпочтительнее стратегий согласования, поскольку большинство из них требует вмешательства пользователя - в просторечии известного как АД ЗАВИСИМОСТИ - и все они включают потенциально неудобные компромиссы в самой логике.

Если мы предположим, что отношения A - ›C и B -› C указаны с использованием версий, а не ветвей или редакций, то стратегии согласования включают:

  • Горец: проанализируйте отношения A- ›C и B-› C, чтобы определить, можно ли безопасно переключить A на использование C-1.1.1 или B можно безопасно переключить на использование C -1.0.3. Если нет, вернитесь к realpolitik.
  • Realpolitik: проанализируйте другие выпущенные / помеченные версии C, чтобы убедиться, что они удовлетворяют требованиям как A, так и B. Если нет, прибегайте к локтевой смазке.
  • Смазка для локтей: разделите вилку / заплатку C и создайте индивидуальную версию, отвечающую требованиям как A, так и B. По крайней мере, вы ДУМАЕТЕ. Наверное, все в порядке. Верно?

Ой, но подожди! Я не учел тот, где Семвер может спасти положение:

  • Позвоните другу: спросите авторов A и B, могут ли они оба договориться об использовании версии C. (Если нет, вернитесь к Highlander.)

Последний, безусловно, , лучший начальный подход. Вместо того, чтобы тратить время на поиски A, B и C достаточно хорошо, чтобы разрешить конфликт самостоятельно, я могу полагаться на сигналы от авторов A и B - людей с наименьшей неуверенностью относительно отношения их проектов к C - чтобы найти компромисс:

В манифесте A говорится, что он может использовать любую версию исправления v1.0 новее 2, а в манифесте B говорится, что он может использовать любую второстепенную версию и версию исправления v1. Это потенциально может разрешить многие версии, но если файл блокировки A указывает на 1.0.3, тогда алгоритм может выбрать это, так как это приведет к наименьшим изменениям.

Теперь это разрешение может не работать. В конце концов, версии - это всего лишь грубая сигнальная система. 1.x - это довольно широкий диапазон, и возможно, что автор Б не спешил его выбрать. Тем не менее, это хорошее место для начала, потому что:

  • Тот факт, что диапазоны semver предлагают решение [я], не означает, что я должен их принимать.
  • Инструмент PDM всегда может дополнительно уточнить совпадения семверов с помощью статического анализа (если возможный статический анализ для данного языка может предложить что-то полезное).
  • Независимо от того, какое из компромиссных решений используется, мне все равно нужно провести интеграционное тестирование, чтобы убедиться, что все соответствует конкретным потребностям моего проекта.
  • Цель всех компромиссных подходов - выбрать приемлемое решение из потенциально большого пространства поиска (такого же размера, как все доступные версии C). Уменьшение размера этого пространства для нулевых усилий полезно, даже если случайные ложные срабатывания разочаровывают.

Тем не менее, наиболее важным является то, что если я выполню работу и обнаружу, что B на самом деле был слишком слабым и включал версии для C, которые не работают (или исключают версии, которые работают), я могу подать патчи против манифеста B, чтобы соответствующим образом изменить диапазон. Такие заплатки фиксируют проделанную вами работу публично и для потомков таким образом, чтобы помочь другим избежать такой же выбоины. Решительно ПЛОХОЕ решение явно ПЛОХОЙ проблемы.

Параметризация зависимостей

Обсуждая манифест, я кратко коснулся возможности разрешения параметризации, которая потребовала бы изменений в графах зависимостей. Если это то, от чего ваш язык выиграет, он может внести некоторые морщины в файл блокировки.

Поскольку цель файла блокировки - полностью и недвусмысленно описать граф зависимостей, параметризация может быстро стать дорогостоящей. Наивный подход будет строить полный граф в памяти для каждой уникальной комбинации параметров; предполагая, что каждый параметр является двоичным включением / выключением, количество требуемых графиков растет экспоненциально (2 ^ N) по количеству параметров. Ой.

Однако этот подход менее «наивен», чем «тупица». Лучшее решение могло бы перечислить все комбинации параметров, разделить их на наборы в зависимости от того, какие комбинации имеют одинаковый входной набор зависимостей, и сгенерировать один график для каждого набора. И еще лучшее решение могло бы обрабатывать все комбинации, находя наименьший набор входных зависимостей, а затем размещая все остальные комбинации наверху в одном многомерном графике. (С другой стороны, в мои лучшие дни я занимаюсь алгоритмизацией-любителем, так что я, вероятно, пропустил большую лодку здесь.)

Может быть, это звучит весело. Или, может быть, это треп, вызывающий головокружение. В любом случае, вариант можно пропустить. Даже если ваш манифест действительно параметризует зависимости, вы всегда можете просто получить все, и компилятор с радостью проигнорирует то, что ему не нужно. И как только ваш PDM неизбежно станет бешено популярным, и вы будете осыпаны приглашениями основных докладчиков конференции, кто-то появится и позаботится об этой сложной части, потому что Open Source.

Это почти все, что касается файлов блокировки. Вперед, к финальному протоколу!

Компилятор, нулевая фаза: привязка к Deps

Если ваш PDM тщательно создает файл блокировки, этот последний шаг может сводиться к блаженно простой генерации кода: прочитать файл блокировки, получить необходимые ресурсы по сети (конечно, через кеш), а затем перетащить их в их красиво инкапсулированы, ничего не беспокоит их место на диске.

Существует два основных подхода к инкапсуляции кода: либо поместить его под дерево исходных текстов, либо выгрузить его в какое-нибудь центральное, удаленное место, где в пути представлены как идентификатор пакета, так и версия. Если последнее возможно, это отличный вариант, поскольку он скрывает фактические зависимые пакеты от пользователя, которому в любом случае не нужно их просматривать. Даже если невозможно использовать центральное расположение напрямую в качестве входных данных компилятора / интерпретатора - вероятно, так обстоит дело с большинством языков - все равно сделайте это и используйте его в качестве кеша. Диск дешевый, и, возможно, вы найдете способ использовать его напрямую позже.

Если ваш PDM относится к последнему, не может централизованно размещаться, вам придется инкапсулировать код зависимости где-то еще. Практически единственное «где-то еще», с которым вы можете даже надеяться не потерпеть неудачу, находится под деревом исходных кодов проекта. (Новые каталоги поставщиков Go полностью удовлетворяют этому требованию.) Это означает, что его следует поместить в область действия системы контроля версий проекта, что сразу же вызывает вопрос: следует ли фиксировать источники зависимостей?

…возможно нет. Есть привередливый технический аргумент против фиксации: если ваш PDM допускает условные зависимости, то какая условная ветвь должна быть зафиксирована? «Эээ, Сэм, obv: просто зафиксируйте их все и позвольте компилятору использовать то, что он хочет». Тогда зачем вам вообще нужно строить параметризованные графики зависимостей для файла блокировки? А что, если для разных условных ветвей нужны разные версии одного и того же пакета? И и…

Понимаете, что я имею в виду? Противный. Кто-то, вероятно, мог бы построить исчерпывающий аргумент в пользу того, чтобы никогда не коммитить источники dep, но кого это волнует? Люди все равно это сделают. Итак, поскольку PDM - это упражнение по снижению вреда, правильный подход гарантирует, что использование Dep-источников является безопасным выбором / ошибкой.

Для PDM, которые напрямую управляют логикой загрузки исходного кода - обычно интерпретируемыми или JIT-скомпилированными языками - извлечение зависимости с ее зафиксированными зависимостями не имеет большого значения: вы можете просто написать загрузчик, который игнорирует эти зависимости в пользу тех, которые у вас есть. -уровневый проект сближает. Однако, если у вас есть язык, такой как Go, где макет файловой системы - это вся игра, депы, которые фиксируют свои зависимости, станут проблемой: они переопределят любую реальность, которую вы пытаетесь создать на верхнем уровне.

Чтобы понять это, давайте кратко обратимся к распределенным системам:

Распределенная система - это система, в которой отказ компьютера, о существовании которого вы даже не подозревали, может сделать ваш компьютер непригодным для использования.
- Лесли Лэмпорт

Если это похоже на ад - вы правы! Все, что не должно вести себя как распределенная система, не должно. Нельзя допустить, чтобы человек А использовал ваш PDM не по назначению, чтобы сборка человека Б не работала. Итак, если языковые ограничения не оставляют вам другого выбора, единственный выход - избавиться от фиксированных проблем при их размещении на диске.

Вот и все. Как и было обещано, довольно просто.

Танец четырех состояний

Хорошо, мы подробно рассмотрели все состояния и протоколы. Теперь мы можем сделать шаг назад и составить общую картину.

Существует базовый набор команд, которые большинство PDM предоставляют пользователям. Используя общие / интуитивно понятные имена, этот список выглядит примерно так:

  • init: Создайте файл манифеста, возможно, наполнив его на основе статического анализа существующего кода.
  • add: Добавьте названные пакеты в манифест.
  • rm: Удалите указанные пакеты из манифеста. (Часто опускается, потому что существуют текстовые редакторы).
  • обновление: обновите закрепленную версию пакета [пакетов] в файле блокировки до последней доступной версии, разрешенной манифестом.
  • install: извлеките и поместите все источники депо, перечисленные в файле блокировки, сначала создав файл блокировки из манифеста, если он не существует.

Наша концепция четырех состояний позволяет легко визуализировать, как каждая из этих команд взаимодействует с системой. (Для краткости давайте также будем сокращать наши состояния до P [код объекта], M [anifest], L [файл ock] и D [код зависимости]):

Прохладный. Но есть очевидные дыры - действительно ли мне нужно запускать две или три отдельные команды (добавить, обновить, установить), чтобы получить пакеты? Это было бы неприятно. И действительно, composer’s require (их эквивалент add) также проталкивает манифест, чтобы обновить блокировку, затем извлекает исходный пакет и выгружает его на диск.

npm, с другой стороны, включает поведение добавить в свою команду install, требуя дополнительных параметров для изменения либо манифеста, либо блокировки. (Как я указывал ранее, npm исторически предпочитал дублированные зависимости / деревья вместо графов; это сделало разумным сосредоточиться в первую очередь на зависимостях вывода, а не следовать потокам состояний, которые я описываю здесь.) Итак, npm требует дополнительного взаимодействия с пользователем для обновления всего. Штаты. Composer делает это автоматически, но справочный текст команды по-прежнему описывает их как отдельные шаги.

Думаю, есть способ получше.

Новая идея: карта, синхронизация, памятка

Примечание: это в глуши. Насколько мне известно, ни один PDM не является столь агрессивным, как этот подход. Мы можем поэкспериментировать с этим в Glide.

Возможно, это было ясно с самого начала, но частью моей мотивации для описания проблемы PDM в терминах состояний и протоколов было создание достаточно чистой модели, чтобы можно было определить протоколы как функции одностороннего преобразования:

  • f: P → M: насколько статический анализ может вывести идентификаторы зависимостей или параметры параметризации из исходного кода проекта, он отображает эту информацию в манифест. Если такой статический анализ невозможен, тогда эта «функция» - это просто ручная работа.
  • f: ML: Преобразует немедленный, возможно, слабо версионный зависимости, перечисленные в манифесте проекта, в полный доступный набор пакетов в графе зависимостей, при этом каждый пакет привязан к одной, идеально неизменяемой версии для каждого пакета.
  • f: LD: преобразует список закрепленных пакетов в файле блокировки в исходный код, расположенный на диске так, как ожидает компилятор / интерпретатор. .

Если все, что мы на самом деле делаем, это сопоставляем входы с выходами, то мы можем определить каждую пару состояний как синхронизированную, если, учитывая существующее входное состояние, функция сопоставления создает существующее выходное состояние. Давайте визуально представим полностью синхронизированную систему следующим образом:

Теперь предположим, что пользователь вручную отредактировал манифест, но еще не запустил команду для обновления файла блокировки. Пока она этого не сделает, M и L не будут синхронизироваться:

Точно так же, если пользователь только что клонировал проект с зафиксированным манифестом и файлом блокировки, тогда мы знаем, что L и D будут не синхронизированы просто потому, что последнего не существует:

Я мог бы перечислить больше, но я думаю, вы поняли идею. В любой момент времени, когда выполняется любая команда PDM, каждый из manifest, lock и deps может находиться в одном из трех состояний:

  • не существует
  • существует, но не синхронизирован с предшественником
  • существует и синхронизируется с предшественником

Построение всего этого таким образом выводит на первый план тонкий принцип дизайна: если эти состояния и протоколы могут быть настолько четко определены, то не глупо ли для PDM когда-либо покинуть государства рассинхронизировались?

Почему да. Да, это так.

Независимо от того, какая команда выполняется, PDM должны стремиться оставить все состояния - или, по крайней мере, последние три - полностью синхронизированными. Независимо от того, что пользователь говорит PDM, он делает, а затем предпринимает все необходимые шаги для обеспечения согласованности системы. При таком подходе, основанном на синхронизации, наша диаграмма команд теперь выглядит следующим образом:

С этой точки зрения доминирующее поведение PDM становится своего рода танцем движение / встречное движение, когда основное действие команды изменяет одно из состояний, а затем система реагирует, чтобы стабилизировать себя.

Вот простой пример: пользователь вручную редактирует манифест, чтобы удалить зависимость, а затем `добавляет другой пакет. В PDM, управляемом только командами пользователя, легко представить, что удаленный вручную пакет не отражается в обновленной блокировке. В PDM на основе синхронизации команда просто добавляет новую запись в манифест, а затем передает управление системе синхронизации.

Здесь есть очевидные преимущества для пользователя: не требуются дополнительные команды или параметры, чтобы все было в порядке. Однако преимущества для самого PDM могут быть менее очевидными: если некоторые команды PDM намеренно завершаются в состоянии рассинхронизации, то следующая команда PDM, которая будет запущена, столкнется с этим и должна решить почему происходит рассинхронизация: это потому, что предыдущая команда оставила это таким образом? Или потому, что пользователь этого хочет? Это удивительно сложный вопрос, на который часто невозможно ответить. Но этого никогда не происходит в PDM на основе синхронизации, поскольку они всегда пытаются перейти к нормальному конечному состоянию.

Конечно, есть (потенциальный) недостаток: предполагается, что единственное ценное состояние - это полная синхронизация. Если пользователю действительно действительно нужна рассинхронизация ... ну, это проблема. Однако я считаю, что рассинхронизация, которую пользователи могут думать, что они хотят сегодня, больше связана с преодолением проблем в их текущем PDM, чем с решением реальной проблемы предметной области. Или, чтобы быть откровенным: извините, но ваш ДПМ вас газлайтинг. Вроде как ваш собственный NoSQL Bane. По сути, я предполагаю, что подход, основанный на синхронизации, устраняет практическую потребность в предоставлении пользователям более детального контроля.

Однако существует (по крайней мере) одна серьезная практическая проблема для PDM на основе синхронизации: производительность. Если ваш файл блокировки ссылается на тридцать сжатых архивов gzip, обеспечение полной синхронизации путем повторного сброса их всех на диск при каждой команде становится недопустимо медленным. Для того, чтобы PDM на основе синхронизации был возможен, он должен иметь дешевые процедуры для определения, синхронизирована ли каждая пара состояний. К счастью, поскольку мы заботимся о том, чтобы определить каждый процесс синхронизации как функцию с четкими входными данными, есть хорошее решение: мемоизация. В процессе синхронизации PDM должен хэшировать входные данные для каждой функции и включать этот хэш как часть выходных данных. В последующих запусках сравнивайте новый входной хэш со старым записанным; если они одинаковы, предположим, что состояния синхронизированы.

На самом деле нас интересуют только ML и LD. Первый вариант довольно прост: входные данные - это список описаний манифеста, плюс параметры, а хэш может быть вставлен в сгенерированный файл блокировки.

Последний немного уродливее. Расчет ввода по-прежнему прост: хешируйте сериализованный график, содержащийся в файле блокировки, или, если ваша система имеет параметризованные зависимости, просто подграф, который вы фактически используете. Проблема в том, что нет элегантного места для хранения этого хэша. Так что не пытайтесь: просто запишите его в файл со специальным именем в корне подкаталога deps. (Если вы - счастливчик, пишущий в центральное место с идентификатором + интервал между именами версий, вы можете все это пропустить.)

Ни один из этих подходов не является надежным, но я предполагаю, что на практике они сработают.

Dénouement

Написание общего руководства по всем соображениям для PDM - непростая задача. Я подозреваю, что пропустил хотя бы одну важную деталь, и уверен, что упустил множество потенциально важных деталей. Комментарии и предложения приветствуются. Я хотел бы, чтобы это руководство было достаточно полным, поскольку управление пакетами является одновременно де-факто требованием для широко распространенного использования языка сегодня и слишком обширной проблемой, чтобы ее можно было адекватно описать в обычных, разрозненных почтовых рассылках. список обсуждений. Я видел, как ряд сообществ изо всех сил пытаются решить эту проблему именно по этой причине.

Тем не менее, я думаю, что рассмотрел существенные аспекты проблемы. Однако в конечном счете технические детали могут быть менее важными, чем общие организационные концепции: управление языковыми пакетами - это упражнение по снижению вреда, выполняемое путем поддержания баланса между людьми и машинами / хаосом и порядком / разработкой и операциями / риском и безопасность / экспериментирование и надежность. Самое главное - поддерживать этот баланс с течением времени.

Поскольку управление во времени - требование, но для языков категорически ужасная идея пытаться управлять своей собственной хронологией источника как частью самого источника, это предполагает, что почти все языки могут извлечь выгоду из PDM. Вернее, один PDM. Языковое сообщество, разделенное взаимно несовместимыми инструментами упаковки, - это непростая ситуация.

Ладно, широкая публика - шу! Или не. В любом случае, пора идти!

PDM для Go

До сих пор в этой статье явно отсутствовали какие-либо tl; dr. Эта часть более конкретна, она получает один:

  • PDM for Go выполнимо, не должно быть так сложно, и в ближайшем будущем может даже хорошо интегрироваться с go get.
  • Центральный реестр пакетов Go может обеспечить множество преимуществ, но любой немедленный план должен работать и без него.
  • Монорепозитории могут быть полезны для внутреннего использования, и PDM должны работать в них, но монорепозитории без реестра вредны для совместного использования открытого кода.
  • У меня есть план действий, который, несомненно, приведет к поразительной победе и великой радости, но чтобы узнать о нем, вам нужно будет прочитать пункты списка в конце

Go много лет боролся с управлением пакетами. У нас были частичные решения и бесконечные дискуссии, но ничего не помогло. Я считаю, что это было так, потому что вся проблема никогда не рассматривалась; безусловно, это было серьезной проблемой, по крайней мере, в последних двух крупных дискуссиях. Может быть, с изложенной общей картиной все уже кажется немного менее туманным.

Во-первых, давайте полностью перейдем от общего случая к Go конкретно. Вот три основных ограничения, которые определяют доступные варианты:

  • Манипуляция GOPATH - ужасная, неустойчивая стратегия. Все мы это знаем. К счастью, в Go 1.6 добавлена ​​поддержка каталога поставщиков, что открывает двери для инкапсулированных сборок и правильно ориентированного на проекты PDM.
  • Компоновщик Go допускает использование только одного пакета для каждого уникального пути импорта. Перезапись пути импорта может обойти это, но Go также имеет переменные уровня пакета и функции инициализации (также известные как глобальное состояние и потенциально неидемпотентная его мутация). Эти два факта делают дублирование пакетов «сломанный ромб» в стиле npm ненадежной стратегией для обработки общих запросов.
  • Без центрального реестра репозитории должны продолжать действовать в качестве единицы обмена. Это довольно неловко пересекается с семантикой Go в отношении отношений каталог / пакет.

Кроме того, при чтении многих дискуссий сообщества, которые происходили на протяжении многих лет, есть две идеи, которые, кажется, часто упускаются:

  • Подходы к управлению пакетами должны быть двойными: как обращенные внутрь (когда ваш проект находится на `` верхнем уровне '' и потребляет другие зависимости), так и обращенные вовне (когда ваш проект является зависимым потребляется другим проектом).
  • Хотя существует некоторое понимание необходимости снижения вреда, слишком много внимания уделяется снижению вреда за счет воспроизводимых сборок, а недостаточно - снижению рисков и неопределенностей, с которыми разработчики сталкиваются в повседневной работе.

Тем не менее, я не думаю, что ситуация ужасная. Вовсе нет. Есть некоторые проблемы, с которыми нужно бороться, но есть и хорошие новости. Сначала хорошие вещи!

Обновление `go get`

Как и во многих других вещах, простой ортогональный дизайн Go делает техническую задачу создания PDM относительно простой (если требования ясны!). Однако еще лучше то, что существует прозрачный, обратно совместимый, инкрементный путь включения в основной набор инструментов. Это даже не сложно описать:

Мы можем определить формат файла блокировки, который соответствует указанным здесь ограничениям, и научить `go get` взаимодействовать с ним. Самым важным аспектом «соответствия» является то, что файл блокировки представляет собой транзитивно полный список зависимостей как для пакета, с которым он находится в каталоге, так и для всех подпакетов - основного и не, тестового и нет, а также включает любые параметризации тегов сборки. . Другими словами, представьте себе следующую схему репозитория:

.git                 # so, this is the repo root
main.go              # a main package
cmd/
  bar/
    main.go          # another main package
foo/
  foo.go             # a non-main package
  foo_amd64.go       # arch-specific, may have extra deps
  foo_test.go        # tests may have extra deps
LOCKFILE             # the lockfile, derp
vendor/              # `go get` puts all (LOCKFILE) deps here

Этот LOCKFILE правильный, если он содержит неизменяемые версии для всех транзитивных зависимостей всех трех (‹root›, foo, bar) пакетов. Таким образом, алгоритм go get выглядит следующим образом:

  1. Вернитесь назад из указанного подпути (например, `go get ‹repoaddr› / cmd / bar`), пока не будет найден LOCKFILE или не будет достигнут корень репозитория.
  2. Если LOCKFILE найден, выгрузить deps в соседний каталог поставщика.
  3. Если LOCKFILE не найден, вернитесь к историческому поведению GOPATH / ветки по умолчанию на основе подсказок.

Это работает, потому что это аналогично тому, как работает сам каталог vendor: для чего-либо в дереве каталогов, имеющего корень в родительском элементе поставщика (здесь корень репо), компилятор будет искать этот каталог поставщика перед поиском GOPATH.

В этом примере я установил блокировку в корне репозитория, но она не обязательно должна быть там. Тот же алгоритм отлично работает в подкаталоге, например:

.git
README.md
src/
  LOCKFILE
  main.go
  foo/
    foo.go

Не совсем лучшие практики, но `go get ‹repoaddr› / src` все равно будет работать с указанным выше алгоритмом.

Этот подход может работать даже в некоторых непонятных ситуациях:

.git
foo.go
bar/
  bar.go
cmd/
  LOCKFILE
  main.go

Если основной пакет импортирует и foo, и bar, то размещение каталога поставщика рядом с LOCKFILE в cmd / означает, что foo и bar не будут им инкапсулированы, и компилятор вернется к импорту из GOPATH. Однако я нашел небольшую хитрость, и похоже, что эту проблему можно решить, поместив копию репозитория в его собственный каталог поставщика:

$GOPATH/github.com/sdboyer/example/
 .git
 foo.go
 bar/
   bar.go
 cmd/
   LOCKFILE
   main.go
   vendor/github.com/sdboyer/example/
       foo.go
       bar/
         bar.go
       cmd/          # The PDM could omit from here on down 
         LOCKFILE
         main.go

Немного странно и болезненно для разработки, но решает проблему полностью автоматически.

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

Если вы просто думаете об удовлетворении конкретных вариантов использования, это место, где легко увязнуть в грязи. Но если мы вернемся к концепции дизайна, согласно которой PDM должны опираться на фундаментальную «единицу обмена», ответ будет более ясным. Пара манифеста и файла блокировки представляют собой границы «единицы» проекта. Хотя они не имеют необходимой связи с исходным кодом проекта, учитывая, что мы имеем дело с файловыми системами, все работает лучше всего, когда они находятся в корне. дерева исходных текстов, в которую входит их «единица».

Это не требует, чтобы они находились в корне репозитория - подкаталог - это нормально, если они являются родительскими для всех пакетов Go, которые они описывают. Настоящая проблема возникает, когда есть желание иметь несколько деревьев или поддеревьев под другой блокировкой (и манифестом). Я пару раз указывал, что совместное использование нескольких пакетов / модулей в одном и том же репозитории невозможно, если репозиторий используется для предоставления временной информации. Это абсолютно верно, но в контексте Go бесполезно просто утверждать, что «не делай этого».

Вместо этого нам нужно провести грань между лучшими практиками публикации проекта и тем, что работает, когда вы строите, скажем, монорепозицию компании. Учитывая повсеместное распространение монорепозиториев на территории Гоглиана, где родился Го, и гравитационное притяжение к монорепозиториям в отношении того, как код публикуется в течение последних нескольких лет, проведение этих линий особенно важно.

Совместное использование, монорепозиторий и наименьшее количество «не делай этого», с которыми я могу справиться

Многие люди, которые столкнулись со средой FLOSS, смотрят на монорепозиторы и спрашивают: аааааааааааааааааааааааааааааааааа более менее чем когда-либо это делать? Но, как и в случае со многими решениями, сделанными технологическими компаниями за миллиард долларов, есть значительные преимущества. Среди прочего, они представляют собой стратегию смягчения некоторых из этих неопределенностей в экосистеме FLOSS. Во-первых, с монорепозиторием можно узнать, что такое весь код. Таким образом, с помощью комплексных строительных систем, возможно… теоретически… отдельные авторы несут системную ответственность за последующие эффекты своих изменений.

Конечно, в монорепозитории весь код находится в одном репозитории. Свертывая весь код в монорепозиторий, подкомпоненты включаются в временную шкалу вселенной монорепозитория и, таким образом, не имеют собственной временной информации - иначе говоря, значимого управления версиями. И это нормально ... пока вы находитесь в одной вселенной и на одной временной шкале. Но, как я надеюсь, теперь ясно, что разумное управление версиями важно для тех, кто не входит в монорепозиторий - тех из нас, кто собирает воедино вселенную на лету. Интересно, что это также относится к людям, которые сшивают другие монорепозитории.

Теперь, если бы у Go был центральный реестр, мы могли бы позаимствовать из зависимостей путей Rust и разработать способ предоставления модуля обмена с независимыми версиями, составленного из подпакетов / поддеревьев репозитория. Если вы сможете преодолеть git / hg / bzr, вызванное Стокгольмским мышлением о том, что« небольшие репозитории - это хорошо, потому что философия Unix », то вы увидите, что в этом есть много хорошего. Вы покажете мне человека, которому нравится поддерживать тонну полусвязанных репозиториев, а я покажу вам мазохиста, робота или лжеца.

Но у Go нет центрального реестра. Может быть, мы сможем сделать это позже. На данный момент, однако, нам нужно принять репозиторий в качестве единицы обмена, принять ограничения на управление версиями, которые накладывают на нас, и разграничить методы, которые подходят для общедоступного репозитория, то есть содержат в себе пакеты, которые некоторые другие репозиторий захочет импортировать - и методы, подходящие для репозиториев (как правило, внутренних монорепозиториев), которые не предназначены для импорта. Назовем первое «открытым», а второе - «закрытым».

  • Открытые репозитории безопасны и разумны для использования в качестве зависимости; закрытых репозиториев нет. (Просто повторяю.)
  • В открытых репозиториях должен быть один манифест, один файл блокировки и один каталог поставщика, в идеале в корне репозитория.
  • Открытые репозитории всегда должны фиксировать свой манифест и блокировку, но не каталог поставщика.
  • В закрытых репозиториях может быть несколько пар манифест / блокировка, но вы должны правильно их организовать.
  • Закрытые репозитории могут безопасно фиксировать каталоги поставщиков.
  • Открыто или закрыто, вы никогда не должны напрямую изменять исходный код в каталоге поставщика. Если вам нужно, разветвите его, отредактируйте и вставьте (или укажите псевдоним) свою вилку.

Хотя я думаю, что это хорошая цель, PDM также должен быть прагматичным: учитывая распространенность монорепозиториев закрытого типа в публичной экосистеме Go (если не что иное, golang.org/x/*), необходимо иметь некоторую поддержку для извлечения пакетов из одного и того же репозитория в разных версиях. И это выполнимо. Но для развития разумной экосистемы, основанной на версиях, сообщество должно либо отказаться от совместного использования закрытых / монорепозиционных репозиториев, либо создать реестр. Все возможные компромиссы, которые я вижу, отбрасывают версионирование под шину, и это не сработает.

Семантическое версионирование и план действий

Прошлой осенью Дэйв Чейни предпринял смелые усилия, чтобы добиться некоторой тяги к внедрению семантического управления версиями. На мой взгляд, обсуждение внесло важный вклад в осознание важности использования системы управления версиями в сообществе. К сожалению (хотя, вероятно, и не ошибочно, поскольку не было конкретных результатов) формальное предложение не удалось.

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

  • Заинтересованные люди должны собраться вместе, чтобы создать и опубликовать общие рекомендации о том, какие типы изменений требуют, какой уровень повышения версии semver.
  • Те же самые заинтересованные люди могли бы собраться вместе, чтобы написать инструмент, который выполняет базовый статический анализ репозитория проекта Go, чтобы выявить соответствующие факты, которые могут помочь в принятии решения о том, какой номер семвера применять изначально. (А затем… автоматически опубликуйте проблему с этой информацией!)
  • Если мы чувствуем себя по-настоящему резвыми, мы могли бы также создать веб-сайт, чтобы отслеживать прогресс в направлении семвертикации Go-dom, чему способствует инструмент анализа и публикации. Табло, хотя и немое, помогает стимулировать коллективные действия!
  • В то же время мы можем рассмотреть самый простой из возможных вариантов - определение файла блокировки только для корня репозитория, который `go get` может читать и прозрачно использовать, если он доступен.
  • По мере распространения semver различные инструменты сообщества могут экспериментировать с различными рабочими процессами и подходами к проблемам монорепорации / управления версиями, но все они по-прежнему соответствуют одному и тому же файлу блокировки.

Управление пакетами в Go не должно быть неразрешимой проблемой. Это просто имеет тенденцию казаться таким, если подходить к нему слишком узко. Хотя есть вещи, которых я не коснулся - управление версиями скомпилированных объектов, статический анализ для уменьшения раздувания депграфов, переопределение локального пути для удобства разработчика - я думаю, что в целом я рассмотрел эту область. Если мы сможем добиться активного прогресса в достижении этих целей, то к концу года мы сможем увидеть кардинально другую экосистему Go - или даже GopherCon!

Go настолько созрел для этого, что это почти болезненно. Простота, возможность быстрого статического анализа… Go PDM легко может стать ярким примером среди своих аналогов. У меня нет проблем с представлением дополнений gofmt / goimports, которые пишут непосредственно в соответствующий манифест, или даже расширяют его возможности локального поиска кода, чтобы включить центральный реестр (если мы его создадим), который может автоматически искать и захватывать нелокальные пакеты. Эти и многие другие вещи очень близки. Не хватает только воли.

Если вы заинтересованы в работе над чем-либо из вышеперечисленного, обращайтесь ко мне. Я уверен, что мы также можем обсудить это в Списке рассылки Go PM.

Спасибо Мэтту Бутчеру, Сэму Ричарду, Ховарду Тайсону, Крису Брандо и Мэтту Фарине за комментарии, обсуждения и предложения по этой статье, а также Кейту Бойеру за ее терпение во всем.