![](https://habrastorage.org/files/dc9/4b6/0ec/dc94b60ec0fa46b3b358c32492bbb8f6.jpeg)
Семантическое версионирование (также известное как SemVer) уже стало популярным подходом к работе с версиями программ и библиотек. Оно не только облегчает работу с последовательными релизами, но позволяет и людям, и автоматике понимать совместимость этих релизов друг с другом и с остальным миром. SemVer можно использовать много где, но больше всего этот подход известен в системах управления зависимостями.
Прежде чем рассказывать о специфичных для Go вещах, давайте посмотрим, что такое семантическое версионирование (примечание переводчика: ради этой части и был затеян перевод).
![](https://habrastorage.org/files/b67/0f8/e31/b670f8e3171c4f5fb7123e930794c6cd.png)
На иллюстрации показаны семантические части строки версии. Чаще всего вы будете видеть только первые три цифры, разделенные точками ".". Полностью же семантическая версия может состоять из следующих частей:
- “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” или даже как “*”. И все эти нотации можно спокойно комбинировать друг с другом.