Anlamsal Sürüm Numaralandırma 2.0.0

Özet

BÜYÜK.KÜÇÜK.YAMA diye belirtilen bir sürüm numaralandırmasında:

  1. Önceki sürüm ile uyumsuz API değişiklikleri yaptığınızda BÜYÜK sürümü,
  2. Önceki sürüm ile uyumlu bir özellik eklediğinizde KÜÇÜK sürümü
  3. Önceki sürüm ile uyumlu hata düzeltmeleri yaptığınızda YAMA sürümünü

yükseltin.

Ayrıca, BÜYÜK.KÜÇÜK.YAMA biçiminin sonuna ön-sunum (pre-release) ve derleme üstverisi (build metadata) gibi etiketler eklenebilmektedir.

Giriş

Yazılım yönetimi dünyasında “imkansız bağımlılıklar” (dependency hell) adında korkulu bir ihtimal vardır. Sisteminiz büyüdükçe ve yazılımınıza daha çok paket eklediğinizde, büyük ihtimalle bir gün gelecek ve kendinizi bu çaresizlik batağında bulacaksınız.

Birçok bağımlılığı olan sistemlerde, yeni bir paket sürümü çıkarmak bir kabusa dönüşebilir. Bağımlılıklar çok sıkı belirlenmişse, sürüm kilitlenmesi (bağımlı olunan tüm paketlerin yeni sürümünü yükseltmeden istenen paketin sürümünü yükseltememek) tehlikesiyle karşı karşıyasınız demektir. Eğer bağımlılıklar çok gevşek belirlenmişse, ister istemez sürüm çeşitliliği (gelecekteki sürümlerle aşırı uyumluluk varsaymak) tarafından etkileneceksiniz. Sürüm kilitlenmesi ve/veya sürüm çeşitliliği nedeniyle projenizi güvenli bir şekilde ilerletemediğinizde imkansız bağımlılıkların içindesiniz demektir.

Bu soruna bir çözüm olarak, sürüm numaralarının nasıl verildiği ve artırıldığı konusunda bazı basit kurallar ve gereklilikler öneriyoruz. Bu kurallar, genel kabul görmüş, hem kapalı hem de açık kaynak yazılım uygulamalarına dayansa da, sadece onlarla sınırlı değildir. Bu numaralandırma sisteminin çalışması için, öncelikle erişime açık bir API (public API) tanımlamanız gerekiyor. Bu, belgeleme veya kod tarafından konulmuş bir kuraldan oluşabilir. Ne şekilde olursa olsun, önemli olan bu API’ın açık ve kesin olmasıdır. Erişime açık API’ınızı tanımladığınız anda, bundan sonra geçireceği değişimleri, sürüm numaranızı belirli artırımlara tabi tutarak iletmelisiniz. Şu şekilde bir sürüm numarası ele alalım: X.Y.Z (Büyük.Küçük.Yama). API’ı etkilemeyen hata düzeltmeleri yama sürümünü artırır, önceki sürümlerle uyumlu API eklemeleri/değişiklikleri küçük sürümü artırır, ve önceki sürümler ile uyumsuz API değişikleri büyük sürümü artırır.

Bu sistemi “Anlamsal Sürüm Numaralandırma” diye nitelendiriyoruz. Bu taslak sayesinde, sürüm numaraları ve sürüm numaralarının değişim şekilleri, kodda, bir sürümden diğerine neyin değiştiğini anlatır hale gelmektedir.

Anlamsal Sürüm Numaralandırma Tanımı (SemVer)

Bu belge içindeki anahtar sözcükler ve/veya eklerinden oluşan “-MALI”, “-MELİ”, “-MAMALI”, “-MEMELİ”, “GEREKLİ”, “-ECEK”, “-ACAK”, “-MEYECEK”, “-MAYACAK”, “-EBİLİR”, ÖNERİLİR”, “OLABİLİR”, ve “İSTEĞE BAĞLI”, RFC 2119‘da belirtildiği şekilde anlaşılmalıdır.

  1. Anlamsal Sürüm Numaralandırma kullanan bir yazılım erişime açık bir API tanımlamalıdır. Bu API, kodun kendi içinde veya yalnızca zaten var olan bir belgelemenin içinde tanımlanabilir. Nasıl yapılırsa yapılsın, kesin ve eksiksiz olmalıdır.

  2. Normal bir sürüm numarası X.Y.Z biçimde OLMALIDIR; burada X, Y, ve Z negatif olmayan tam sayılardır ve başlarında sıfır İÇERMEMELİDİR. X büyük sürümdür, Y küçük sürümdür ve Z yama sürümüdür. Her öğe sayısal olarak ARTMALIDIR. Örnek: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. Numaralandırılmış bir paket sunulduğunda, o sürümün içeriği DEĞİŞTİRİLMEMELİDİR. Yapılan herhangi bir değişim yeni bir sürüm olarak sunulmalıdır.

  4. Sıfır olan Büyük sürüm başlangıçta yapılan yazılım geliştirme içindir. Her şey her an değişebilir. Erişime açık API dengeli (stable) diye değerlendirilmemelidir.

  5. 1.0.0 sürümü erişime açık API’ı tanımlar. Sürüm numarasının bu sürümden sonra nasıl artırılacağı bu erişime açık API’a ve nasıl değiştiğine bağlıdır.

  6. Yama sürümü Z (x.y.Z | X > 0) sadece önceki sürüm ile uyumlu hata düzeltmeleri yapıldığında ARTIRILMALIDIR. Hata düzeltmesi şöyle tanımlanır: Yanlış bir davranışı düzelten içsel bir değişiklik.

  7. Küçük sürüm Y (x.Y.z | x > 0), erişime açık API’a yeni, önceki sürüm ile uyumlu bir özellik eklendiğinde ARTIRILMALIDIR. Erişime açık API’daki bir özellik artık kullanmayan (deprecated) olarak işaretlendiğinde ARTIRILMALIDIR. Özel koda ciddi oranda yeni özellikler veya geliştirmeler katıldığında ARTTIRILABİLİR. Yama düzeyindeki değişiklikler buna dahil EDİLEBİLİR. Yama sürümü, küçük sürüm artırıldığında SIFIRLANMALIDIR.

  8. Büyük sürüm X (X.y.z | X > 0) önceki sürüm ile uyumsuz değişiklikler yapıldığında ARTIRILMALIDIR. Yama ve küçük düzeydeki değişiklikler buna dahil edilebilir. Büyük sürüm artırıldığında yama ve küçük sürüm SIFIRLANMALIDIR.

  9. Bir ön-sunum (pre-release) sürümü, yama sürümünden hemen sonraki kısımda, bir tire işaretiyle ve bir dizi nokta ayracıyla GÖSTERİLEBİLİR. Tanımlayıcılar yalnızca ASCII alfasayısal ve tire işaretlerinden [0-9A-Za-z-] OLUŞMALIDIR. Tanımlayıcılar boş OLMAMALIDIR. Sayısal tanımlayıcılar öncül sıfırlar İÇERMEMELİDİR. Ön-sunum sürümleri ilişkili normal sürümden daha düşük önceliğe sahiptir. İlişkili normal sürümün tersine, bir ön-sunum sürümü, sürümün kararsız olduğunu ve beklenen uyumluluk gereksinimlerini karşılayamayacağını gösterir. Örnekler: 1.0.0-ilk, 1.0.0-ilk.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.

  10. Derleme üst verisi, yama sürümünü veya ön-sunum sürümünü hemen takip ederek eklenen bir artı işaretiyle ve bir dizi nokta ayracıyla GÖSTERİLEBİLİR. Tanımlayıcılar yalnızca ASCII alfasayısal ve tire işaretlerinden [0-9A-Za-z-] OLUŞMALIDIR. Tanımlayıcılar boş OLMAMALIDIR. Derleme üstverisi sürüm önceliği belirlenirken dikkate ALINMAMALIDIR. Böylece, yalnızca derleme üstverisiyle farklılık gösteren iki sürüm de aynı önceliğe sahip olacaktır. Örnekler: 1.0.0-ilk+001, 1.0.0+20130313144700, 1.0.0-ikincil+deneme.sha.5114f85.

  11. Öncelik, sürümlerin sıralandıklarında, birbirleriyle sıralarının nasıl karşılaştırılacaklarını belirtir.

Öncelik sürüm numarası büyük, küçük, yama ve ön-sunum tanımlayıcılarıyla, burada yazıldığı sırada (Derleme üstverisi öncelik belirlenirken anlamsızdır) ayırarak HESAPLANMALIDIR.

Öncelik, soldan sağa doğru tanımlayıcıların her birini şu şekilde karşılaştırırken belirlenir: Büyük, küçük, ve yama sürümleri daima sayısal olarak karşılaştırılır.

Örnek: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.

Büyük, küçük ve yama eşit olduğunda, bir ön-sunum sürümü, normal bir sürümden daha düşük önceliğe sahiptir.

Örnek: 1.0.0-ilk < 1.0.0.

Aynı büyük, küçük ve yama sürümüne sahip iki ön-sunum sürümünün önceliği, şu şekilde gösterildiği gibi, soldan sağa doğru her bir tanımlayıcıyı ayırıp karşılaştırarak BELİRLENMELİDİR:

Yalnızca rakamlardan oluşan tanımlayıcılar sayısal olarak karşılaştırılır

Harfli veya tire çizgili tanımlayıcılar ise ASCII sözcük sıralamalarına göre karşılaştırılırlar.

Sayısal tanımlayıcılar, sayısal olmayan tanımlayıcılardan daima daha düşük önceliğe sahiptirler.

Önce gelen tüm tanımlayıcıları eşitse, büyük bir dizi ön-sunumun alanlarının, daha küçük bir dizininkinden daha yüksek önceliği vardır.

Örneğin: 1.0.0-ilk < 1.0.0-ilk.1 < 1.0.0-ilk.ikincil < 1.0.0-ikincil < 1.0.0-ikincil.2 < 1.0.0-ikincil.11 < 1.0.0-sa.1 < 1.0.0.

Backus ― Naur formu grameri için geçerli SemVer versiyonları


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

Anlamsal Sürüm Numaralandırma Neden Kullanılmalıdır?

Aslında, bu yeni veya çığır açan bir fikir değil. Büyük olasılıkla buna yakın bir şeyler zaten yapıyorsunuzdur. Sorun şudur: “yakın” olması yeterince iyi değildir. Bir şekilde belirlenmiş, resmi bir şartnameye uymadan, sürüm sayıları bağımlılık yönetimi için gerçek hayatta kullanışsızdır. Üstteki fikirleri isimlendirip, açık tanımlamalar yaparak, yazılımınızın kullanıcılarına niyetinizi iletmeniz kolaylaşır. Niyetler bir kere açık, esnek olduğunda (fakat aşırı esnekleştirmeden), bağımlılık tanımlamaları yapılabilir hale gelir.

Basit bir örnek Anlamsal Sürüm Numaralandırma’nın imkansız bağımlılıklar nasıl geçmişin bir parçası haline getirebildiğini gösterecek. “İtfaiye Aracı” isminde bir kütüphaneyi ele alalım. “Merdiven” adındaki Anlamsal Sürüm ile Numaralandırılmış bir pakete ihtiyaç duymaktadır. İtfaiye Aracı yaratıldığında, Merdiven 3.1.0 sürümündedir. İtfaiye Aracı 3.1.0’da eklenmiş bazı işlevlere ihtiyaç duyduğundan, Merdiven bağımlılığının 3.1.0’a eşit ya da daha büyük fakat 4.0.0’dan küçük olduğunu güvenle belirtebilirsiniz. Böylelikle, Merdiven sürüm 3.1.1 ve 3.2.0 kullanılabilir olduğunda, paket yönetim sisteminize bunları sunabilirsiniz ve halihazırdaki bağımlılığı bulunduğu yazılımla uyumlu olacağını bilirsiniz.

Sorumluluk sahibi bir yazılım geliştirici olarak, tabii ki her paket güncellemesinin duyurulduğu şekilde işlediğini kontrol etmek isteyeceksiniz. Gerçek dünya karışık bir yerdir; gözünüzü açık tutmak dışında yapabileceğiniz pek bir şey yoktur. Ancak, yapabileceğiniz şudur: Bağımlı olunan paketlerin yeni sürümlerini çıkarmaya gerek kalmadan paketleri sunmak ve yükseltmek için sizi zaman ve eziyetten kurtaracak mantıklı bir yol öneren Anlamsal Sürüm Numaralandırma’yi kullanmaktır.

Bütün bunlar size uygunsa, Anlamsal Sürüm Numaralandırma’yi kullanmaya başlamak için yapmanız gereken tek şey kullanmaya başladığınızı ve kurallarını takip ettiğinizi duyurmaktır. BENİOKU (README) dosyanızdan bu web sitesine bağlantı vererek diğerlerinin de kuralları bilmesini ve bunlardan yarar sağlamalarını sağlayabilirsiniz.

SSS

İlk geliştirme fazı olan 0.y.z’deki değişikliklerle nasıl başa çıkmalıyım?

Yapılabilecek en basit şey ilk geliştirme sürümünüzü 0.1.0’da başlatmaktır ve takip eden her sürüm için küçük sürümü artırmaktır.

1.0.0’ı ne zaman sunacağımı nasıl anlarım?

Yazılımınız kullanıma hazır (production) ortamında kullanılmaya başlanmışsa, sürümü zaten muhtemelen 1.0.0’dır. Kullanıcıların güvenebildiği kararlı bir API’a sahipseniz, 1.0.0’da olmalısınız. Önceki sürüm ile uyumlu olmayı dert ediyorsanız, büyük olasılıkla, çoktandır 1.0.0 olmalısınız.

Anlamsal Sürüm Numaralandırma, çabuk geliştirmeden ve hızlı özyinelemeden caydırmaz mı?

Büyük sürümün sıfır olması tamamen seri geliştirmeyle alakalıdır. Eğer API’ı her gün değiştiriyorsanız ya hala 0.y.z sürümü ya da sıradaki büyük sürümün üzerinde çalıştığınız ayrı bir geliştirme dalı (branch) üzerinde olmalısınız.

Erişime açık API’ımın önceki bir sürümüyle uyumsuz en küçük değişiklikler bile Büyük sürümü artıracaksa, kendimi birden 42.0.0 sürümünde bulmayacak mıyım?

Bu sorumluluk sahibi yazılım geliştirmekle ve ileri görüşlülükle alakalı bir sorudur. Birçok bağımlılığı olan bir yazılıma uyumsuz değişiklikler öylesine eklenmemelidir. Maruz kalınacak yükseltme maliyeti ciddi boyutlarda olabilir. Uyumsuz değişikliklerde büyük sürümü artırmak demek yaptığınız değişikliklerin etkileri hakkında etraflıca düşüneceğinizi, ve maliyet/kazanç oranına göre karar vermenizi gerektirecektir.

Erişime açık API’ın tamamını belgelendirmek büyük iş!

Başkaları tarafından kullanılacağı düşünülen bir yazılımı düzgün bir şekilde belgelendirmek usta bir yazılım geliştirici olarak sizin sorumluluğunuzdur. Yazılım karmaşıklığını yönetmek bir projeyi verimli tutmanın çok önemli bir kısmıdır ve kimse yazılımınızı nasıl kullanacağını ya da hangi yöntemleri (methods) çağırmanın güvenli olacağını bilemiyorsa bunu yapmak zordur. Anlamsal Sürüm Numaralandırma, uzun dönemde, ve iyi tanımlanmış erişime açık bir API ile herkesin ve her şeyin sorunsuzca devam etmesini sağlar.

Önceki sürüm ile uyumsuz bir değişikliği yanlışlıkla küçük bir sürüm olarak sunarsam ne yaparım?

Anlamsal Sürüm Numaralandırma şartnamesini bozduğunuzu farkettiğiniz anda, sorunu düzeltin ve sorunu düzelten ve önceki sürüm le uyumluluğunu sağlayan yeni bir küçük sürüm sunun. Bu şartlar altında bile, numaralandırılmış sunumları kesinlikle değiştirmeyin. Mümkünse, sıkıntı çıkaran sürümü belgelendirin ve kullanıcılarınızı sorunla alakalı olarak bilgilendirin böylece sıkıntı çıkaran sürümden haberleri olmuş olur.

Erişime açık API’ı değiştirmeden yazılımın bağımlılıklarını güncellersem ne yapmalıyım?

Erişime açık API’ı etkilemediğinden uyumlu olarak değerlendirilebilecek bir durumdur. Paketinizle aynı bağımlılıklara sahip olan bir yazılımın kendi bağımlılık şartnameleri olmalıdır ve yazılımcısı tüm uyuşmazlıkları farkedecektir. Bir hatayı düzeltmek ya da yeni bir işlev kazandırmak için bağımlılıklarınızı güncelleyip güncellemediğiniz, yama ya da küçük seviyede bir değişiklik yapıp yapmadığınızı belirler. İkinci örnekteki durum için genelde ilave kod bekleriz ki bu her iki durum da küçük seviye bir artırır.

Ya yanlışlıkla erişime açık API’ı sürüm numarasıyla uyumlu olmayacak bir şekilde değiştirmişsem (örn: yama sunumunda büyük bir kırılım oluşturan kod)?

Sağduyunuzu kullanın. Erişime açık API’ın davranışını beklenen, önceki haline geri getirmenizden etkilenecek büyük bir kullanıcı kitleniz varsa, düzeltmeniz bir yama sunumu gibi değerlendirilebilir olsa da, büyük bir sürüm sunumu yapmak en iyi seçenek olabilir. Anlamsal Sürüm Numaralandırma’nin tüm amacının sürüm numaralarının nasıl değiştiğini ifade ettiğini unutmayın. Eğer bu değişiklikler kullanıcılarınız için önemliyse, sürüm numarasını kullanarak onları bilgilendirin.

Artık kullanılamayacak bir işlevle nasıl başa çıkabilirim?

Var olan işlevlerin çürümesi yazılım geliştirmenin normal bir parçasıdır ve ileri adım atabilmek için genellikle gerekir. Erişime açık API’ınızın bir kısmını artık kullanılmayacak şekilde geliştirdiğinizde, iki şey yapmalısınız: (1) değişiklik konusunda kullanıcılarınızı bilgilendirmek için belgelendirmenizi güncelleyin, (2) artık kullanmayan yöntemleri içeren küçük bir sunum çıkarın. İşlevi büyük bir sunumla tamamen kaldırmadan önce, kullanıcılarınızın yeni API’a kolayca geçiş yapabilmeleri için artık kullanılmayan yöntemleri içeren en az bir adet küçük bir sunum olmalıdır.

SemVer, sürüm karakterlerinde (string) bir sınıra sahip midir?

Hayır, fakat sağduyunuzu kullanın. Örneğin, 255 karaktere sahip bir sürüm karakteri muhtemelen gereksizdir. Ayrıca, karakterlerin uzunluğu konusunda bazı sistemler kendi sınırlarını koyabilirler.

“v1.2.3” bir Anlamsal Sürüm Numaralandırma mıdır?

Hayır, “v1.2.3” bir anlamsal sürüm bu maralandırma değildir. Yine de, bir anlamsal sürüm numaralandırmayı bir “v” (İngilizcede )ile başlatmak bunun bir versiyon numaralandırması olduğunu gösteren ortak bir yoldur. Versiyonu “v” olarak kısaltmak versiyon kontrolünde sıklıkla görülür. Örneğin: git tag v1.2.3 -m “Release version 1.2.3”, bu durumda “v1.2.3” bir etiket ismi ve “1.2.3” bir anlamsal sürüm numaralandırmadır.

SemVer kontrolü için önerilen bir regular expression (RegEx) var mıdır?

İki tane var. Adlandırılmış gruplara sahip birisi şunları destekler. (PCRE [Perl Compatible Regular Expressions, i.e. Perl, PHP and R], Python ve Go)

Bakınız: 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-]+)*))?$

Numaralandırılmış yakalama gruplarına sahip (yani cg1 = major, cg2 = minor, cg3 = patch, cg4 = prerelease ve cg5 = buildmetadata kullanmak yerine) ECMA Script (JavaScript),(Perl Compatible Regular Expressions, vb. Perl, PHP and R), Python ve Go destekleyen diğeri.

Bakınız: 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-]+)*))?$

Hakkında

Anlamsal Sürüm Numaralandırma şartnamesi, Gravatar’ların kaşifi ve GitHub’un kurucu ortaklarından olan Tom Preston-Werner tarafından yazılmıştır.

Geribildirim vermek isterseniz, lütfen GitHub’da bir konu açın.

Lisans

Creative Commons ― CC BY 3.0