Лаборатория кодера

Справочник · Код и сборка

Семантическое версионирование: как читать и ставить номера версий

Что значат три числа в версии, как сравниваются предварительные выпуски и как устроены диапазоны в npm, pip и Cargo.

Обновлено · 6 мин чтения

Семантическое версионирование (SemVer 2.0.0) — договорённость о том, как номер версии сообщает о совместимости. Если библиотека ему следует, по одному номеру понятно, можно ли обновиться без правок в своём коде.

Формат

text
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
   2  .  7  .  1   -rc.2        +sha.5114f85
ЧастьКогда увеличиватьПример
MAJORнесовместимые изменения публичного API1.9.3 → 2.0.0
MINORновая функциональность с обратной совместимостью1.9.3 → 1.10.0
PATCHисправления ошибок без изменения API1.9.3 → 1.9.4

При увеличении старшей части младшие сбрасываются в ноль. Числа не дополняются нулями слева: 1.02.0 — недопустимая версия. Каждое число сравнивается как число, поэтому 1.10.0 новее 1.9.0.

Что считается публичным API

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

Типичные несовместимые изменения, которые требуют новой MAJOR:

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

Добавить необязательный параметр, новую функцию или поле в ответ — MINOR. Пометить функцию устаревшей, но оставить её рабочей — тоже MINOR.

Версия 0.x

Пока MAJOR равен нулю, API считается нестабильным и может меняться в любой момент. На практике менеджеры пакетов трактуют 0.MINOR как «старшую» часть: переход 0.4.2 → 0.5.0 считается потенциально ломающим. Выпуск 1.0.0 означает: API зафиксирован, дальше правила действуют полностью.

Предварительные выпуски и сборка

После дефиса идёт метка предварительного выпуска из идентификаторов через точку: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-beta.11, 1.0.0-rc.1. Такая версия считается младше обычной с теми же тремя числами.

Порядок сравнения меток:

  1. Идентификаторы сравниваются слева направо по одному.
  2. Числовые сравниваются как числа: beta.2 < beta.11.
  3. Буквенно-цифровые — посимвольно в порядке ASCII: alpha < beta < rc.
  4. Числовой идентификатор всегда младше буквенного: 1.0.0-1 < 1.0.0-alpha.
  5. Если все общие части равны, короче — значит младше: alpha < alpha.1.

Полная цепочка из стандарта:

text
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta
  < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

Метаданные сборки после + при сравнении не учитываются: 1.0.0+20261103 и 1.0.0+sha.abc — одна и та же версия по приоритету. Туда пишут дату сборки или хеш коммита.

Диапазоны в npm

ЗаписьЧто подходитКомментарий
1.4.2только 1.4.2точная версия
^1.4.2>=1.4.2 <2.0.0по умолчанию при npm install
^0.4.2>=0.4.2 <0.5.0для 0.x каретка строже
^0.0.3только 0.0.3
~1.4.2>=1.4.2 <1.5.0только исправления
1.4.x, 1.4>=1.4.0 <1.5.0
>=1.2 <1.6пересечение условий
1.2.0 - 1.5.0>=1.2.0 <=1.5.0диапазон через дефис с пробелами
^1.2 || ^2.0любой из диапазонов

Предварительные выпуски в диапазон по умолчанию не попадают: ^1.2.0 не выберет 1.3.0-beta.1. Исключение — если в самом диапазоне указан предварительный выпуск той же тройки чисел.

Python, Cargo и Go

ЭкосистемаЗаписьЗначение
piprequests>=2.31,<3явный диапазон
piprequests~=2.31>=2.31, ==2.*
piprequests~=2.31.0>=2.31.0, ==2.31.*
piprequests==2.31.*любые исправления 2.31
Cargoserde = "1.0"то же, что ^1.0
Cargorand = "=0.8.5"точная версия
Gorequire example.com/lib v1.4.2минимальная версия, выбирается наименьшая подходящая

У Python своя схема версий (PEP 440), она похожа на SemVer, но предварительные выпуски пишутся без дефиса: 2.0.0a1, 2.0.0b2, 2.0.0rc1, а ещё есть .post1 и .dev3.

Lock-файлы

Диапазон в манифесте говорит, что можно поставить, а lock-файл (package-lock.json, poetry.lock, Cargo.lock, go.sum) — что поставлено на самом деле, с точными версиями и хешами. Его коммитят для приложений, чтобы сборка на сервере и у коллег была одинаковой. Для библиотек Cargo.lock и package-lock.json часто не публикуют: конечный пользователь всё равно подберёт свои версии.

Как выпускать версию

bash
git switch main && git pull
# обновить номер в манифесте и список изменений
git commit -am "Выпуск 2.4.0"
git tag -a v2.4.0 -m "Выпуск 2.4.0"
git push origin main v2.4.0

Если коммиты подписаны префиксами feat: и fix:, а ломающие изменения отмечены ! или строкой BREAKING CHANGE:, следующий номер можно вычислить автоматически: есть ломающее — MAJOR, есть feat — MINOR, иначе PATCH.

Версии вне SemVer

Не все проекты следуют семантическому версионированию, и это нормально, если правила записаны:

СхемаПримерСмысл
календарная (CalVer)2026.11, 24.04год и месяц выпуска: Ubuntu, pip, многие сервисы
сборочная1.0.4512номер сборки растёт с каждым выпуском
маркетинговая11, 2026номер для пользователей, а не для совместимости

Для приложений, у которых нет публичного API (сайт, мобильное приложение), календарная схема часто честнее: она говорит, насколько версия свежая, и не делает вид, что обещает совместимость.

Частые вопросы

  • Можно ли перевыпустить ту же версию? Нет. Опубликованная версия неизменна, ошибку исправляют новым выпуском.
  • Выпустил ломающее изменение в MINOR. Выпустите следующий PATCH, который возвращает совместимость, и затем MAJOR с этим изменением.
  • Как называть ветку для исправления старой версии? Обычно release/1.4 или support/1.x: исправление делают в ней, выпускают 1.4.7, а затем переносят в main.
  • Нужен ли v перед номером? В метках git — общепринято (v1.2.3), но сам номер версии — 1.2.3.