Semantičko Verzionisanje 2.0.0

Sažetak

Za dati broj verzije MAJOR.MINOR.PATCH, inkrementirajte:

  1. MAJOR (GLAVNU) verziju kada unesete nekompatibilne izmene API-ja
  2. MINOR (MANJU) verziju kada dodate unazad kompatibilnu funkcionalnost
  3. PATCH (ZAKRPA) verziju kada dodate unazad kompatibilne ispravke bug-ova (grešaka)

Dodatne oznake za pre-release (predizdanja) i metapodatke build-a (izrade) dostupne su kao proširenja u formatu MAJOR.MINOR.PATCH.

Uvod

U svetu upravljanja softverom postoji užasno mesto koje nazivamo „pakao zavisnosti“. Kako vaš sistem raste i što više paketa integrišete u svoj softver, veća je verovatnoća da ćete se naći u ovom stanju dubokog očaja.

U sistemima sa mnoštvom zavisnosti, objavljivanje novih verzija paketa može brzo postati košmar. Ako su specifikacije zavisnosti suviše stroge, nalazite se u opasnosti od zaključavanja verzije (nemogućnost nadogradnje paketa bez neophodne objave nove verzije svakog zavisnog paketa). Takođe, ako su specifikacije zavisnosti isuviše labave, neizbežno će vas dovesti u situaciju verzijskog promiskuiteta (pod pretpostavkom kompatibilnosti sa više budućih verzija nego što je razumno). Pakao zavisnosti je situacija u kojoj se nalazite kada zaključavanje verzije i/ili verzijski promiskuitet sprečavaju jednostavno i bezbedno napredovanje projekta.

Kao rešenje ovog problema, predlažemo jednostavan skup pravila i zahteva koji diktiraju kako se brojevi verzija dodeljuju i inkrementiraju. Ova pravila su zasnovana, ali nisu nužno ograničena na već postojeće i široko rasprostranjene uobičajene prakse koje se koriste u closed i open-source softveru. Kako bi ovaj sistem funkcionisao, neophodno je prvo objaviti public (javni) API. Možemo to primeniti u dokumentaciji ili u samom kodu. U svakom slučaju, važno je da API bude jasan i precizan. Jednom kad identifikujemo javni API, izmene prenosimo kroz specifikovane inkrementacije broja verzije. Razmotrimo format verzije X.Y.Z (Major.Minor.Patch). Ispravke bug-ova (grešaka) koji ne utiču na API inkrementiraju patch (zakrpa) verziju, unazad kompatibilne promene API-ja inkrementiraju minor (manju) verziju, a unazad nekompatibilne promene API-ja inkrementiraju major (glavnu) verziju.

Ovaj sistem nazivamo „Semantičko Verzionisanje“. Prema ovoj šemi, brojevi verzija i način na koji se menjaju daju informacije o kodu koji se nalazi pod datom verzijom, kao i šta se menjalo od jedne verzije do druge.

Specifikacija Semantičkog Verzionisanja (SemVer)

Ključne reči “MUST” (“MORA”), “MUST NOT” (“NE SME”), “REQUIRED” (“NEOPHODNO”), “SHALL” (“HOĆE”), “SHALL NOT” (“NEĆE”), “SHOULD” (“TREBA”), “SHOULD NOT” (“NE TREBA”), “RECOMMENDED” (“PREPORUČENO”), “MAY” (“MOŽE”) i “OPTIONAL” (“OPCIONO”) u ovom dokumentu treba tumačiti kako je opisano u RFC 2119.

  1. Softver koji koristi Semantičko Verzionisanje MUST (MORA) objaviti public (javni) API. Ovaj API može biti deklarisan u samom kodu ili postojati striktno u dokumentaciji. U svakom slučaju, SHOULD (TREBA) da bude precizan i sveobuhvatan.

  2. Normalan broj verzije MUST (MORA) biti u formatu X.Y.Z gde su X, Y i Z ne-negativni celi brojevi koji MUST NOT (NE SMEJU) počinjati sa nulom. X označava glavnu verziju, Y manju verziju, a Z zakrpu. Svaki element MUST (MORA) se numerički inkrementirati. Na primer: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. Jednom kad je verzionisani paket objavljen, sadržaj te verzije MUST NOT (NE SME) se menjati. Svaka izmena MUST (MORA) se objaviti kao nova verzija.

  4. Major (glavna) verzija nula (0.y.z) je za inicijalni razvoj. Bilo šta se MAY (MOŽE) menjati u svakom trenutku. Taj public (javni) API SHOULD NOT (NE TREBA) se smatrati stabilnim.

  5. Verzija 1.0.0 definiše public (javni) API. Način na koji će se broj verzije inkrementirati nakon ove objave zavisi od ovog public (javnog) API-ja i izmena na njemu.

  6. Patch (zakrpa) verzija Z (x.y.Z | x > 0) MUST (MORA) se inkrementirati samo kada se dodaju unazad kompatibilne ispravke bug-ova (grešaka). Ispravke bug-ova (grešaka) su definisane kao interne promene koje ispravljaju nepravilno ponašanje.

  7. Minor (manja) verzija Y (x.Y.z | x > 0) MUST (MORA) se inkrementirati ako je nova unazad kompatibilna funkcionalnost uvedena u javni API. MUST (MORA) se inkrementirati kada se neka od funkcionalnosti API-ja označi kao deprecated (zastarela). MAY (MOŽE) biti inkrementirana ukoliko se uvedu substancijalno nove funkcionalnosti ili poboljšanja u okviru privatnog koda. MAY (MOŽE) uključivati patch (zakrpa) promene. Patch (zakrpa) verzija MUST (MORA) se resetovati na 0 kada se minor (manja) verzija inkrementira.

  8. Major (glavna) verzija X (X.y.z | X > 0) MUST (MORA) se inkrementirati ako se unazad nekompatibilne promene uvode u javni API. MAY (MOŽE) uključivati i minor (manje) i patch (zakrpa) promene. Patch (zakrpa) i minor (manje) verzije MUST (MORA) da se resetuju na 0 kada se major (glavna) verzija inkrementira.

  9. Verzija pre-release (predizdanja) MAY (MOŽE) biti označena dodavanjem hyphen-a (povlake) i niza identifikatora odvojenih tačkom neposredno nakon patch (zakrpa) verzije. Identifikatori MUST (MORAJU) sadržati samo ASCII alfanumeričke znakove i povlake [0-9A-Za-z-]. Identifikatori MUST NOT (NE SMEJU) biti prazni. Numerički identifikatori MUST NOT (NE SMEJU) počinjati nulom. Verzije predizdanja imaju niži prioritet od povezane normalne verzije. Verzija predizdanja označava da je verzija nestabilna i da možda neće biti zadovoljeni predviđeni zahtevi kompatibilnosti kao što je označeno njenom povezanom normalnom verzijom. Primeri: 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. Metapodaci build-a (izrade) MAY (MOGU) biti označeni dodavanjem plus znaka i niza identifikatora odvojenih tačkom neposredno nakon patch (zakrpe) ili verzije predizdanja. Identifikatori MUST (MORAJU) sadržati samo ASCII alfanumeričke znakove i povlake [0-9A-Za-z-]. Identifikatori MUST NOT (NE SMEJU) biti prazni. Metapodaci build-a (izrade) MUST (MORAJU) se zanemariti prilikom određivanja prioriteta verzije. Dve verzije koje se razlikuju samo u metapodacima build-a (izrade), imaju isti prioritet. Primeri: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD.

  11. Prioritet se odnosi na način kojim se verzije u međusobno porede.

    1. Prioritet se MUST (MORA) izračunati razdvajanjem verzije na major (glavnu), minor (manju), patch (zakrpu) i identifikatore predizdanja (metapodaci build-a (izrade) nemaju ulogu u određivanju prioriteta).

    2. Prioritet se određuje prvom razlikom kada se upoređuje svaki od identifikatora sa leva na desno: Major (glavna), minor (manja) i patch (zakrpa) verzije se uvek upoređuju brojčano.

      Primer: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.

    3. Kada su major (glavna), minor (manja) i patch (zakrpa) jednake, verzija predizdanja ima niži prioritet od normalne verzije:

      Primer: 1.0.0-alpha < 1.0.0.

    4. Prioritet između dve verzije predizdanja sa jednakom major (glavnom), minor (manjom) i patch (zakrpa) verzijom MUST (MORA) biti određen upoređivanjem svakog identifikatora odvojenog tačkama sa leva na desno dok se ne pronađe razlika na sledeći način:

      1. Identifikatori koji se sastoje samo od cifara upoređuju se numerički.

      2. Identifikatori sa slovima ili povlakama se upoređuju leksički u ASCII poretku.

      3. Numerički identifikatori uvek imaju niži prioritet od nenumeričkih identifikatora.

      4. Veći skup oznaka predizdanja ima viši prioritet od manjeg skupa ako su svi prethodni identifikatori jednaki.

      Primer: 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.

Bakus–Naurova forma za validne SemVer verzije

<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"

Zašto koristiti Semantičko Verzionisanje?

Ovo nije nova niti revolucionarna ideja. Tačnije, verovatno već radite nešto vrlo slično. Problem je što nešto “slično” nije dovoljno dobro. Bez usaglašenosti sa nekom vrstom formalne specifikacije, brojevi verzija su u suštini beskorisni za upravljanje zavisnostima. Davanjem imena i jasnih definicija gore navedenih ideja, postaje lako preneti svoje namere korisnicima vašeg softvera. Jednom kada su ove namere jasne, konačno je moguće napraviti fleksibilne (ali, ne previše fleksibilne) specifikacije zavisnosti.

Jednostavan primer može pokazati kako pakao zavisnosti uz Semantičko Verzionisanje ostaje stvar prošlosti. Zamislite library (biblioteku) pod nazivom “Vatrogasno_vozilo”. Neophodan joj je Semantičko Verzionisani paket pod nazivom “Merdevine.” U trenutku kreiranja Vatrogasno_vozilo, Merdevine su u verziji 3.1.0. Pošto Vatrogasno_vozilo koristi neke funkcije prvobitno uvedene u 3.1.0, možete bezbedno specifikovati zavisnost od Merdevine kao veću ili jednaku od 3.1.0, ali manju od 4.0.0. Sada, kada Merdevine verzije 3.1.1 i 3.2.0 postanu dostupne, možete ih uneti u svoj sistem upravljanja paketima i biti sigurni da će biti kompatibilni sa postojećim zavisnim softverom.

Kao odgovoran programer, vi ćete, naravno, želeti da verifikujete da li upgrade (nadgradnje) paketa funkcionišu kako je navedeno. Stvarni svet je mahom neuređeno mesto; ne možemo ništa uraditi povodom toga sem da budemo oprezni. Ono što možemo učiniti je da usvojimo Semantičko Verzionisanje koje nam pruža razuman način za objavljivanje i nadogradnju paketa, bez potrebe za pokretanjem novih verzija zavisnih paketa, štedeći vreme i trud.

Ako vam ovo zvuči poželjno, sve što je potrebno uraditi da biste počeli da koristite Semantičko Verzionisanje je da se deklarišete kao korisnik i da potom sledite pravila. Povežite ovaj sajt sa svojim README fajlom kako bi i drugi bili svesni pravila i imali koristi od njih.

FAQ

Kako se nositi sa revizijama 0.y.z u inicijalnoj fazi razvoja?

Najjednostavniji način je da započnete inicijalni razvoj objavom verzije 0.1.0 i potom inkrementirate minor (manju) verziju za svako sledeće izdanje.

Kako da znam kada treba objaviti verziju 1.0.0?

Ako se softver koristi u produkciji, već bi verovatno trebao biti 1.0.0. Ako već imate stabilni API od kojeg korisnici zavise, verzija bi trebala biti 1.0.0. Ukoliko ste prilično zabrinuti oko kompatibilnosti unazad, softver bi već trebao biti objavljen pod verzijom 1.0.0.

Zar ovo ne obeshrabruje rapidan razvoj i brzu iteraciju?

Major (glavna) verzija nula je zapravo predodređena rapidnom razvoju. Ako menjate API svaki dan, trebali bi ostati na verziji 0.y.z ili na posebnoj grani za razvoj raditi na sledećoj major (glavnoj) verziji.

Ukoliko i najsitnije unazad nekompatibilne izmene u javnom API-ju zahtevaju uvećavanje major (glavne) verzije, neću li vrlo brzo doći do verzije 42.0.0?

Ovo je pitanje odgovornog razvoja i predviđanja. U softver koji ima puno zavisnog koda, nekompatibilne izmene ne treba olako uvoditi. Troškovi nadogradnje mogu biti značajni. Ako morate povećati major (glavnu) verziju kako biste objavili verziju sa nekompatibilnim izmenama, morate razmisliti o uticaju tih izmena i proceniti odnos uključenih troškova i koristi.

Dokumentacija celokupnog javnog API-ja zahteva previše posla!

Vaša odgovornost kao profesionalnog programera je da pravilno dokumentujete softver koji je namenjen korisnicima. Upravljanje složenošću softvera je izuzetno važan deo održavanja efikasnosti projekta, a to je teško uraditi ako niko ne zna kako da koristi softver niti koje metode može bezbedno pozvati. Dugoročno, Semantičko Verzionisanje i insistiranje na kvalitetno definisanom API-ju omogućiće da svi i sve radi glatko.

Šta ukoliko slučajno objavim unazad nekompatibilne izmene kao minor (manju) verziju?

Čim primetite da ste prekršili specifikacije Semantičkog Verzionisanja, ispravite grešku i objavite novu minor (manju) verziju koja ispravlja problem i vraća kompatibilnost unazad. Čak i u takvim uslovima, nije prihvatljivo modifikovati verzionisane objave. Ako je prikladno, dokumentujte verziju koja krši specifikaciju i obavestite korisnike kako bi bili svesni toga.

Šta učiniti ukoliko izmenim sopstvene zavisnosti bez promene javnog API-ja?

Takve izmene smatramo kompatibilnim jer ne utiču na javni API. Softver koji eksplicitno zavisi od istih zavisnosti kao i vaš sopstveni paket, treba imati vlastite specifikacije zavisnosti, a autor će primetiti eventualne konflikte. Da li je promena na nivou patch (zakrpa) ili minor (manje) verzije zavisi od toga da li ste ažurirali svoje zavisnosti kao ispravke bug-ova (grešaka) ili ste ih uveli kao nove funkcionalnosti. U drugom slučaju možemo očekivati i dodatni kod, pri čemu se očigledno radi o inkrementu minor (manje) verzije.

Šta ukoliko slučajno izmenim javni API na način koji ne odgovara izmeni broja verzije (npr. uvedem nekompatibilnu major (glavnu) izmenu u okviru patch-a (zakrpe))?

Koristite svoju najbolju procenu. Ako imate veliki broj korisnika na koje će promena javnog API-ja na željeno ponašanje značajno uticati, najbolje je da objavite major (glavnu) verziju, iako bi se ispravak mogao smatrati patch-om (zakrpom). Zapamtite, svrha Semantičkog Verzionisanja je prenošenje značenja putem izmene broja verzije. Ako su takve izmene važne za vaše korisnike, koristite broj verzije da biste ih informisali.

Kako postupati sa deprecating (zastarelim) funkcionalnostima?

Postojeće funkcionalnosti koje zastarevaju sastavni su deo razvoja softvera i često su neophodne kako bi razvoj napredovao. Kad označavate deo javnog API-ja kao deprecated (zastareli), potrebno je pratiti dve stvari: (1) ažurirati dokumentaciju kako biste informisali korisnike, (2) objaviti novu minor (manju) verziju sa definisanim deprecated (zastarelim) delovima softvera. Pre nego što potpuno uklonite funkcionalnost u novoj major (glavnoj) verziji, potrebno je izdati barem jednu minor (manju) verziju koja sadrži deprecated (zastarele) delove, kako bi korisnici nesmetano prešli na novu verziju API-ja.

Ima li SemVer ograničenje za veličinu stringa verzije?

Ne, ali procenite sami. String verzije od 255 znakova je verovatno preteran, na primer. Takođe, neki sistemi mogu imati svoja ograničenja veličine stringa.

Da li je “v1.2.3” semantička verzija?

Ne, “v1.2.3” nije semantička verzija. Međutim, dodavanje prefiksa “v” na semantičku verziju je uobičajen način (na engleskom) da se naznači da je to broj verzije. Skraćenje “version” na “v” se često vidi u kontroli verzija. Primer: git tag v1.2.3 -m "Release version 1.2.3", gde je “v1.2.3” naziv tag-a (oznake), a semantička verzija je “1.2.3”.

Da li postoji predloženi regularni izraz (RegEx) za proveru SemVer stringa?

Postoje dva. Jedan sa imenovanim grupama za one sisteme koji ih podržavaju (PCRE [Perl Compatible Regular Expressions (Perl kompatibilni regularni izrazi), tj. Perl, PHP i R], Python i Go).

Pogledajte: 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-]+)*))?$

I drugi sa numerisanim grupama (znači cg1 = major (glavna), cg2 = minor (manja), cg3 = patch (zakrpa), cg4 = prerelease (predizdanje) i cg5 = buildmetadata (metapodaci)) koji su kompatibilni sa ECMA Script (JavaScript), PCRE [Perl Compatible Regular Expressions (Perl kompatibilni regularni izrazi), tj. Perl, PHP i R], Python i Go.

Pogledajte: 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-]+)*))?$

O projektu

Autor specifikacije Semantičkog Verzionisanja je Tom Preston-Werner, pronalazač Gravatar-a i suosnivač GitHub-a.

Ako želite ostaviti povratne informacije, molimo otvorite issue na GitHub-u.

Licenca

Creative Commons ― CC BY 3.0