Семантичко Верзионисање 2.0.0

Сажетак

За дати број верзије MAJOR.MINOR.PATCH, инкрементирајте:

  1. MAJOR (ГЛАВНУ) верзију када унесете некомпатибилне измене API-ја
  2. MINOR (МАЊУ) верзију када додате уназад компатибилну функционалност
  3. PATCH (ЗАКРПА) верзију када додате уназад компатибилне исправке bug-ова (грешака)

Додатне ознаке за pre-release (предиздања) и метаподатке build-а (израде) доступне су као проширења у формату MAJOR.MINOR.PATCH.

Увод

У свету управљања софтвером постоји ужасно место које називамо „пакао зависности“. Како ваш систем расте и што више пакета интегришете у свој софтвер, већа је вероватноћа да ћете се наћи у овом стању дубоког очаја.

У системима са мноштвом зависности, објављивање нових верзија пакета може брзо постати кошмар. Ако су спецификације зависности сувише строге, налазите се у опасности од закључавања верзије (немогућност надоградње пакета без неопходне објаве нове верзије сваког зависног пакета). Такође, ако су спецификације зависности исувише лабаве, неизбежно ће вас довести у ситуацију верзијског промискуитета (под претпоставком компатибилности са више будућих верзија него што је разумно). Пакао зависности је ситуација у којој се налазите када закључавање верзије и/или верзијски промискуитет спречавају једноставно и безбедно напредовање пројекта.

Као решење овог проблема, предлажемо једноставан скуп правила и захтева који диктирају како се бројеви верзија додељују и инкрементирају. Ова правила су заснована, али нису нужно ограничена на већ постојеће и широко распрострањене уобичајене праксе које се користе у closed и open-source софтверу. Како би овај систем функционисао, неопходно је прво објавити public (јавни) API. Можемо то применити у документацији или у самом коду. У сваком случају, важно је да API буде јасан и прецизан. Једном кад идентификујемо јавни API, измене преносимо кроз спецификоване инкрементације броја верзије. Размотримо формат верзије X.Y.Z (Major.Minor.Patch). Исправке bug-ова (грешака) који не утичу на API инкрементирају patch (закрпа) верзију, уназад компатибилне промене API-ја инкрементирају minor (мању) верзију, а уназад некомпатибилне промене API-ја инкрементирају major (главну) верзију.

Овај систем називамо „Семантичко Верзионисање“. Према овој шеми, бројеви верзија и начин на који се мењају дају информације о коду који се налази под датом верзијом, као и шта се мењало од једне верзије до друге.

Спецификација Семантичког Верзионисања (SemVer)

Кључне речи “MUST” (“МОРА”), “MUST NOT” (“НЕ СМЕ”), “REQUIRED” (“НЕОПХОДНО”), “SHALL” (“ХОЋЕ”), “SHALL NOT” (“НЕЋЕ”), “SHOULD” (“ТРЕБА”), “SHOULD NOT” (“НЕ ТРЕБА”), “RECOMMENDED” (“ПРЕПОРУЧЕНО”), “MAY” (“МОЖЕ”) и “OPTIONAL” (“ОПЦИОНО”) у овом документу треба тумачити како је описано у RFC 2119.

  1. Софтвер који користи Семантичко Верзионисање MUST (МОРА) објавити public (јавни) API. Овај API може бити декларисан у самом коду или постојати стриктно у документацији. У сваком случају, SHOULD (ТРЕБА) да буде прецизан и свеобухватан.

  2. Нормалан број верзије MUST (МОРА) бити у формату X.Y.Z где су X, Y и Z не-негативни цели бројеви који MUST NOT (НЕ СМЕЈУ) почињати са нулом. X означава главну верзију, Y мању верзију, а Z закрпу. Сваки елемент MUST (МОРА) се нумерички инкрементирати. На пример: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. Једном кад је верзионисани пакет објављен, садржај те верзије MUST NOT (НЕ СМЕ) се мењати. Свака измена MUST (МОРА) се објавити као нова верзија.

  4. Major (главна) верзија нула (0.y.z) је за иницијални развој. Било шта се MAY (МОЖЕ) мењати у сваком тренутку. Тај public (јавни) API SHOULD NOT (НЕ ТРЕБА) се сматрати стабилним.

  5. Верзија 1.0.0 дефинише public (јавни) API. Начин на који ће се број верзије инкрементирати након ове објаве зависи од овог public (јавног) API-ја и измена на њему.

  6. Patch (закрпа) верзија Z (x.y.Z | x > 0) MUST (МОРА) се инкрементирати само када се додају уназад компатибилне исправке bug-ова (грешака). Исправке bug-ова (грешака) су дефинисане као интерне промене које исправљају неправилно понашање.

  7. Minor (мања) верзија Y (x.Y.z | x > 0) MUST (МОРА) се инкрементирати ако је нова уназад компатибилна функционалност уведена у јавни API. MUST (МОРА) се инкрементирати када се нека од функционалности API-ја означи као deprecated (застарела). MAY (МОЖЕ) бити инкрементирана уколико се уведу субстанцијално нове функционалности или побољшања у оквиру приватног кода. MAY (МОЖЕ) укључивати patch (закрпа) промене. Patch (закрпа) верзија MUST (МОРА) се ресетовати на 0 када се minor (мања) верзија инкрементира.

  8. Major (главна) верзија X (X.y.z | X > 0) MUST (МОРА) се инкрементирати ако се уназад некомпатибилне промене уводе у јавни API. MAY (МОЖЕ) укључивати и minor (мање) и patch (закрпа) промене. Patch (закрпа) и minor (мање) верзије MUST (МОРА) да се ресетују на 0 када се major (главна) верзија инкрементира.

  9. Верзија pre-release (предиздања) MAY (МОЖЕ) бити означена додавањем hyphen-а (повлаке) и низа идентификатора одвојених тачком непосредно након patch (закрпа) верзије. Идентификатори MUST (МОРАЈУ) садржати само ASCII алфанумеричке знакове и повлаке [0-9A-Za-z-]. Идентификатори MUST NOT (НЕ СМЕЈУ) бити празни. Нумерички идентификатори MUST NOT (НЕ СМЕЈУ) почињати нулом. Верзије предиздања имају нижи приоритет од повезане нормалне верзије. Верзија предиздања означава да је верзија нестабилна и да можда неће бити задовољени предвиђени захтеви компатибилности као што је означено њеном повезаном нормалном верзијом. Примери: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92, 1.0.0-x-y-z.--.

  10. Метаподаци build-а (израде) MAY (МОГУ) бити означени додавањем плус знака и низа идентификатора одвојених тачком непосредно након patch (закрпе) или верзије предиздања. Идентификатори MUST (МОРАЈУ) садржати само ASCII алфанумеричке знакове и повлаке [0-9A-Za-z-]. Идентификатори MUST NOT (НЕ СМЕЈУ) бити празни. Метаподаци build-а (израде) MUST (МОРАЈУ) се занемарити приликом одређивања приоритета верзије. Две верзије које се разликују само у метаподацима build-а (израде), имају исти приоритет. Примери: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD.

  11. Приоритет се односи на начин којим се верзије у међусобно пореде.

    1. Приоритет се MUST (МОРА) израчунати раздвајањем верзије на major (главну), minor (мању), patch (закрпу) и идентификаторе предиздања (метаподаци build-а (израде) немају улогу у одређивању приоритета).

    2. Приоритет се одређује првом разликом када се упоређује сваки од идентификатора са лева на десно: Major (главна), minor (мања) и patch (закрпа) верзије се увек упоређују бројчано.

      Пример: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.

    3. Када су major (главна), minor (мања) и patch (закрпа) једнаке, верзија предиздања има нижи приоритет од нормалне верзије:

      Пример: 1.0.0-alpha < 1.0.0.

    4. Приоритет између две верзије предиздања са једнаком major (главном), minor (мањом) и patch (закрпа) верзијом MUST (МОРА) бити одређен упоређивањем сваког идентификатора одвојеног тачкама са лева на десно док се не пронађе разлика на следећи начин:

      1. Идентификатори који се састоје само од цифара упоређују се нумерички.

      2. Идентификатори са словима или повлакама се упоређују лексички у ASCII поретку.

      3. Нумерички идентификатори увек имају нижи приоритет од ненумеричких идентификатора.

      4. Већи скуп ознака предиздања има виши приоритет од мањег скупа ако су сви претходни идентификатори једнаки.

      Пример: 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.

Бакус–Наурова форма за валидне SemVer верзије

<valid semver> ::= <version core>
                 | <version core> "-" <pre-release>
                 | <version core> "+" <build>
                 | <version core> "-" <pre-release> "+" <build>

<version core> ::= <major> "." <minor> "." <patch>

<major> ::= <numeric identifier>

<minor> ::= <numeric identifier>

<patch> ::= <numeric identifier>

<pre-release> ::= <dot-separated pre-release identifiers>

<dot-separated pre-release identifiers> ::= <pre-release identifier>
                                          | <pre-release identifier> "." <dot-separated pre-release identifiers>

<build> ::= <dot-separated build identifiers>

<dot-separated build identifiers> ::= <build identifier>
                                    | <build identifier> "." <dot-separated build identifiers>

<pre-release identifier> ::= <alphanumeric identifier>
                           | <numeric identifier>

<build identifier> ::= <alphanumeric identifier>
                     | <digits>

<alphanumeric identifier> ::= <non-digit>
                            | <non-digit> <identifier characters>
                            | <identifier characters> <non-digit>
                            | <identifier characters> <non-digit> <identifier characters>

<numeric identifier> ::= "0"
                       | <positive digit>
                       | <positive digit> <digits>

<identifier characters> ::= <identifier character>
                          | <identifier character> <identifier characters>

<identifier character> ::= <digit>
                         | <non-digit>

<non-digit> ::= <letter>
              | "-"

<digits> ::= <digit>
           | <digit> <digits>

<digit> ::= "0"
          | <positive digit>

<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
           | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
           | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
           | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
           | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
           | "y" | "z"

Зашто користити Семантичко Верзионисање?

Ово није нова нити револуционарна идеја. Тачније, вероватно већ радите нешто врло слично. Проблем је што нешто “слично” није довољно добро. Без усаглашености са неком врстом формалне спецификације, бројеви верзија су у суштини бескорисни за управљање зависностима. Давањем имена и јасних дефиниција горе наведених идеја, постаје лако пренети своје намере корисницима вашег софтвера. Једном када су ове намере јасне, коначно је могуће направити флексибилне (али, не превише флексибилне) спецификације зависности.

Једноставан пример може показати како пакао зависности уз Семантичко Верзионисање остаје ствар прошлости. Замислите library (библиотеку) под називом “Vatrogasno_vozilo”. Неопходан јој је Семантичко Верзионисани пакет под називом “Merdevine.” У тренутку креирања Vatrogasno_vozilo, Merdevine су у верзији 3.1.0. Пошто Vatrogasno_vozilo користи неке функције првобитно уведене у 3.1.0, можете безбедно спецификовати зависност од Merdevine као већу или једнаку од 3.1.0, али мању од 4.0.0. Сада, када Merdevine верзије 3.1.1 и 3.2.0 постану доступне, можете их унети у свој систем управљања пакетима и бити сигурни да ће бити компатибилни са постојећим зависним софтвером.

Као одговоран програмер, ви ћете, наравно, желети да верификујете да ли upgrade (надградње) пакета функционишу како је наведено. Стварни свет је махом неуређено место; не можемо ништа урадити поводом тога сем да будемо опрезни. Оно што можемо учинити је да усвојимо Семантичко Верзионисање које нам пружа разуман начин за објављивање и надоградњу пакета, без потребе за покретањем нових верзија зависних пакета, штедећи време и труд.

Ако вам ово звучи пожељно, све што је потребно урадити да бисте почели да користите Семантичко Верзионисање је да се декларишете као корисник и да потом следите правила. Повежите овај сајт са својим README фајлом како би и други били свесни правила и имали користи од њих.

FAQ

Како се носити са ревизијама 0.y.z у иницијалној фази развоја?

Најједноставнији начин је да започнете иницијални развој објавом верзије 0.1.0 и потом инкрементирате minor (мању) верзију за свако следеће издање.

Како да знам када треба објавити верзију 1.0.0?

Ако се софтвер користи у продукцији, већ би вероватно требао бити 1.0.0. Ако већ имате стабилни API од којег корисници зависе, верзија би требала бити 1.0.0. Уколико сте прилично забринути око компатибилности уназад, софтвер би већ требао бити објављен под верзијом 1.0.0.

Зар ово не обесхрабрује рапидан развој и брзу итерацију?

Major (главна) верзија нула је заправо предодређена рапидном развоју. Ако мењате API сваки дан, требали би остати на верзији 0.y.z или на посебној грани за развој радити на следећој major (главној) верзији.

Уколико и најситније уназад некомпатибилне измене у јавном API-ју захтевају увећавање major (главне) верзије, нећу ли врло брзо доћи до верзије 42.0.0?

Ово је питање одговорног развоја и предвиђања. У софтвер који има пуно зависног кода, некомпатибилне измене не треба олако уводити. Трошкови надоградње могу бити значајни. Ако морате повећати major (главну) верзију како бисте објавили верзију са некомпатибилним изменама, морате размислити о утицају тих измена и проценити однос укључених трошкова и користи.

Документација целокупног јавног API-ја захтева превише посла!

Ваша одговорност као професионалног програмера је да правилно документујете софтвер који је намењен корисницима. Управљање сложеношћу софтвера је изузетно важан део одржавања ефикасности пројекта, а то је тешко урадити ако нико не зна како да користи софтвер нити које методе може безбедно позвати. Дугорочно, Семантичко Верзионисање и инсистирање на квалитетно дефинисаном API-ју омогућиће да сви и све ради глатко.

Шта уколико случајно објавим уназад некомпатибилне измене као minor (мању) верзију?

Чим приметите да сте прекршили спецификације Семантичког Верзионисања, исправите грешку и објавите нову minor (мању) верзију која исправља проблем и враћа компатибилност уназад. Чак и у таквим условима, није прихватљиво модификовати верзионисане објаве. Ако је прикладно, документујте верзију која крши спецификацију и обавестите кориснике како би били свесни тога.

Шта учинити уколико изменим сопствене зависности без промене јавног API-ja?

Такве измене сматрамо компатибилним јер не утичу на јавни API. Софтвер који експлицитно зависи од истих зависности као и ваш сопствени пакет, треба имати властите спецификације зависности, а аутор ће приметити евентуалне конфликте. Да ли је промена на нивоу patch (закрпа) или minor (мање) верзије зависи од тога да ли сте ажурирали своје зависности као исправке bug-ова (грешака) или сте их увели као нове функционалности. У другом случају можемо очекивати и додатни код, при чему се очигледно ради о инкременту minor (мање) верзије.

Шта уколико случајно изменим јавни API на начин који не одговара измени броја верзије (нпр. уведем некомпатибилну major (главну) измену у оквиру patch-а (закрпе))?

Користите своју најбољу процену. Ако имате велики број корисника на које ће промена јавног API-ја на жељено понашање значајно утицати, најбоље је да објавите major (главну) верзију, иако би се исправак могао сматрати patch-ом (закрпом). Запамтите, сврха Семантичког Верзионисања је преношење значења путем измене броја верзије. Ако су такве измене важне за ваше кориснике, користите број верзије да бисте их информисали.

Како поступати са deprecating (застарелим) функционалностима?

Постојеће функционалности које застаревају саставни су део развоја софтвера и често су неопходне како би развој напредовао. Кад означавате део јавног API-ја као deprecated (застарели), потребно је пратити две ствари: (1) ажурирати документацију како бисте информисали кориснике, (2) објавити нову minor (мању) верзију са дефинисаним deprecated (застарелим) деловима софтвера. Пре него што потпуно уклоните функционалност у новој major (главној) верзији, потребно је издати барем једну minor (мању) верзију која садржи deprecated (застареле) делове, како би корисници несметано прешли на нову верзију API-ја.

Има ли SemVer ограничење за величину стринга верзије?

Не, али процените сами. Стринг верзије од 255 знакова је вероватно претеран, на пример. Такође, неки системи могу имати своја ограничења величине стринга.

Да ли је “v1.2.3” семантичка верзија?

Не, “v1.2.3” није семантичка верзија. Међутим, додавање префикса “v” на семантичку верзију је уобичајен начин (на енглеском) да се назначи да је то број верзије. Скраћење “version” на “v” се често види у контроли верзија. Пример: git tag v1.2.3 -m "Release version 1.2.3", где је “v1.2.3” назив tag-а (ознаке), а семантичка верзија је “1.2.3”.

Да ли постоји предложени регуларни израз (RegEx) за проверу SemVer стринга?

Постоје два. Један са именованим групама за оне системе који их подржавају (PCRE [Perl Compatible Regular Expressions (Perl компатибилни регуларни изрази), тј. Perl, PHP и R], Python и Go).

Погледајте: https://regex101.com/r/Ly7O1x/3/

^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

И други са нумерисаним групама (znači cg1 = major (главна), cg2 = minor (мања), cg3 = patch (закрпа), cg4 = prerelease (предиздање) и cg5 = buildmetadata (метаподаци)) који су компатибилни sa ECMA Script (JavaScript), PCRE [Perl Compatible Regular Expressions (Perl компатибилни регуларни изрази), тј. Perl, PHP и R], Python и Go.

Погледајте: https://regex101.com/r/vkijKf/1/

^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

О пројекту

Аутор спецификације Семантичког Верзионисања је Tom Preston-Werner, проналазач Gravatar-а и суоснивач GitHub-а.

Ако желите оставити повратне информације, молимо отворите issue на GitHub-у.

Лиценца

Creative Commons ― CC BY 3.0