Усилия и деньги, вкладываемые в продвижение языка Go, часто приносят пользу и другим разработчикам. В конце прошлого года на сайте gopheracademy была опубликована очень удачная статья о семантическом версионировании. Том самом, которое используется в npm, начинается с домика ^ и все ломает. Под катом спрятан перевод, который поможет вам быстро осмотреть сад граблей версионирования и как сейчас принято им пользоваться. И немного примеров на Go. Передаем слово автору!
Семантическое версионирование (также известное как SemVer) уже стало популярным подходом к работе с версиями программ и библиотек. Оно не только облегчает работу с последовательными релизами, но позволяет и людям, и автоматике понимать совместимость этих релизов друг с другом и с остальным миром. SemVer можно использовать много где, но больше всего этот подход известен в системах управления зависимостями.
Прежде чем рассказывать о специфичных для Go вещах, давайте посмотрим, что такое семантическое версионирование (примечание переводчика: ради этой части и был затеян перевод).
На иллюстрации показаны семантические части строки версии. Чаще всего вы будете видеть только первые три цифры, разделенные точками ".". Полностью же семантическая версия может состоять из следующих частей:
- “Major” номер версии. Увеличивается (и не всегда на 1, вспомните PHP 5 -> 7), когда API библиотеки или приложения меняется обратно несовместимым образом.
- “Minor” номер версии. Увеличивается, когда в API добавляются новые функции без нарушения обратной совместимости (примечание переводчика: вот здесь самое зло. К примеру, npm по умолчанию сохраняет версию с префиксом “^”, что означает “любая версия с такой же major”. Авторы npm резонно полагают, что если при смене minor обратная совместимость не теряется, то можно смело обновлять. Сюрприз: множество разработчиков библиотек просто не знают, что такое семантическое версионирование и ломают обратную совместимость, как получится. Результат – на компьютере автора все работает, а при установке “с чистого листа” на другом компьютере мы получаем новые версии зависимостей с поломанной обратной совместимостью). При увеличении номера “major” принято сбрасывать “minor” в 0, то есть за версией 4.27… последует версия 5.0… а не 5.28
- “Patch” номер версии. Увеличивается при исправлении багов, рефакторинге и прочих изменениях, которые ничего не ломают, но и новых фичей не добавляют. При увеличении “Major” или “Minor” принято сбрасывать “Patch” в 0 (примечание переводчика: очень часто его называют “build number” и просто увеличивают на единицу с каждый билдом continuous integration, никогда не сбрасывая. Это позволяет легко отличать билды друг от друга во время тестирования и анализа сообщений ошибок от пользователей. Минус такого подхода в том, что довольно скоро цифра становится пятизначной. Но многих это не беспокоит).
- Строка “pre-release”: необязательный, разделенный точками список, отделенный от трех номеров версии знаком минус. Например: “1.2.3-beta.2”. Используется вместо тегов, чтобы “помечать” определенные вехи в разработке. Обычно это “alpha”, “beta”, “release candidate” (“rc”) и производные от них.
- Завершает строку семантической версии метаданные системы сборки. Так же, как и список “pre-release” тегов, он разделен точками ., но отделяет от номеров или тегов его не минус, а плюс. Данную информацию принято игнорировать (примечание переводчика: по феншую номер билда должен идти сюда – но мало кто так делает, очень длинные строки получаются).
Несмотря на то, что в спецификации ничего не говорится о префиксе “v”, он часто используется перед строкой семантической версии, например, “v.12.3”. Так же, как и метаданные, его принято игнорировать.
Все это и многое другое можно найти в официальной спецификации: http://semver.org/
Благодаря продуманной спецификации, строки семантических версий можно легко распарсить, сортировать, и, главное, сравнивать друг с другом и с диапазонами «допустимых» версий. Чем, собственно, и занимаются большинство менеджеров зависимостей, таких как npm.
Парсинг семантических версий в Go
Для Go доступно несколько пакетов для работы с семантическими версиями. В этой статье я рассмотрю вот этот: github.com/Masterminds/semver. Он соответствует спецификации, поддерживает необязательный префикс “v”, сортировку, работу с диапазонами и ограничениями. При этом с ограничениями этот пакет работает так же, как большинство решений для других языков программирования, таких как JavaScript, Rust и другие.
Пример ниже парсит строку семантической версии и выводит либо “major” версию, либо сообщение об ошибке:
v, err := semver.NewVersion("1.2.3-beta.1+build345")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(v.Major())
}
Возвращаемое значение является экземпляром semver.Version, который содержит некоторое количество полезных методов. Если переданная строка не является семантической версией, то возвращается ошибка semver.ErrInvalidSemVer.
Но реальная польза этой библиотеки заключается не в возможности парсить строки, а в возможности проводить сложные действия над семантическими действиями.
Сортировка семантических версий
С помощью библиотеки semver вы можете сортировать семантические версии средствами стандартной библиотеки. Например:
raw := []string{"1.2.3", "1.0", "1.0.0-alpha.1" "1.3", "2", "0.4.2",}
vs := make([]*semver.Version, len(raw))
for i, r := range raw {
v, err := semver.NewVersion(r)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
vs[i] = v
}
sort.Sort(semver.Collection(vs))
В этом примере набор семантических версий преобразуется в экземпляры semver.Version, которые затем складываются в semver.Collection. А у semver.Collection есть все необходимое для использования со стандартной библиотекой sort. Это очень удобно для правильной сортировки pre-release информации и игнорирования мета-тегов.
Диапазоны, Ограничения и Wildcards
Один из самых популярных вопросов относительно версий заключается в проверке, лежит ли версия в указанном диапазоне. Или удовлетворяет ли она каким-то другим ограничениям. Все эти проверки легко сделать с помощью библиотеки:
c, err := semver.NewConstraint(">= 1.2.3, < 2.0.0, != 1.4.5")
if err != nil {
fmt.Println("Error parsing constraint:", err)
return
}
v, err := semver.NewVersion("1.3")
if err != nil {
fmt.Println("Error parsing version:", err)
return
}
a := c.Check(v)
fmt.Println("Version within constraint:", a)
Для тех, кто знаком с диапазонами версий по другим языкам и тулзам, библиотека предлагает хорошо знакомую нотацию:
- ^1.2.3 обозначает совместимость на уровне “major” версии. Это синтаксический сахар для записи “>= 1.2.3, < 2.0.0”. Используется для указания “самой свежей версии зависимости, все еще совместимой по API”.
- ~1.2.3 обозначает совместимость на уровне “patch” версии. Это синтаксический сахар для “>= 1.2.3, < 1.3.0”. Используется для указания нужной версии с последними багфиксами, но без серьезных изменений.
- “1.2.3 — 3.4.5” обозначает точный диапазон версий, включая начальную и конечную. Это синтаксический сахар для “>= 1.2.3, <= 3.4.5”.
- Также библиотека поддерживает “wildcards” с помощью символов “x”, ”X” или “*”. Вы можете указать версию как “2.x”, “1.2.x” или даже как “*”. И все эти нотации можно спокойно комбинировать друг с другом.
Начните использовать семантические версии прямо сейчас
И ваши волосы станут мягкими и шелковистыми! Если в вашем проекте есть работа с версиями, то предлагаю не терять время и начать пользоваться стандартом семантического версионирования. Для Go есть описанная выше библиотека github.com/Masterminds/semver, большинству современных языков и тулчейнов тоже есть, что предложить. Особенно Node.js с npm.
Автор: Voximplant