Ders 06 / 14
Anlamlı Sürümleme
Sürüm numarasının üç parçasının taşıdığı söz, karşılaştırma ve öncelik kuralları, ön yayın etiketleri, sürüm aralıklarının sınırları ve kırıcı değişikliğin tanımı.
İçindekiler
Önceki ders, bildirimdeki ^1.4.0 gibi bir aralığın tek bir sürüm değil bir küme
belirttiğini söyledi. Kümenin sınırlarını çizen şey, sürüm numarasının parçalarına anlam
yükleyen bir sözleşmedir.
Bu sözleşmeye anlamlı sürümleme (semantic versioning) denir. Teknik bir mekanizma değil, bir söz verme biçimidir: numarayı yükselten taraf, tüketiciye ne yaptığını söyler. Mekanizma olan kısım — karşılaştırma ve aralık hesabı — sözleşmenin üzerine kurulur.
Üç Parçanın Verdiği Söz
Sürüm numarası noktalarla ayrılmış üç sayıdan oluşur: ana (major), ikincil (minor) ve yama (patch). Bir yayın hazırlanırken hangi parçanın artırılacağı, yapılan değişikliğin türüne bağlıdır:
| Değişiklik | Artan parça | Tüketiciye verilen söz |
|---|---|---|
| Hata düzeltmesi, iç iyileştirme | Yama | Kodunuz değişmeden çalışır |
| Geriye dönük uyumlu yetenek eklemesi | İkincil | Kodunuz değişmeden çalışır, yeni yetenek de vardır |
| Var olan davranışı değiştiren düzenleme | Ana | Kodunuz değişebilir; geçiş notlarını okuyun |
Sözün konusu, paketin açık arayüzüdür: dışa aktarım haritasında listelenen yollar ve o yollardan erişilen adlar. Haritada olmayan bir iç dosyanın yeniden adlandırılması ana sürümü artırmaz — kapsüllemenin karşılığı budur. Bu, Programlama Temelleri kursunda tanıtılan geriye dönük uyumluluk kavramının paket düzeyinde bir sözleşmeye dönüştürülmesidir.
Sözleşmenin kendisi hiçbir şey zorlamaz. Numarayı doğru artırmak yazarın sorumluluğudur; tüketici tarafında ise numaraya duyulan güvenin sınırı, bir sonraki bölümdeki aralıkların neden savunmasız olduğunu açıklar.
Karşılaştırma ve Öncelik
Aralık hesabının önkoşulu, iki sürümün karşılaştırılabilmesidir. Karşılaştırma metin
sırasıyla yapılamaz: 1.10.0 sürümü 1.9.0 sürümünden büyüktür ama metin olarak küçük
görünür. Linux’a Giriş kursundaki paket yöneticisi dersinde aynı sorun sistem paketleri
için anlatılmıştı; burada kural daha dardır ve tümüyle yazılıdır.
Kural üç adımdır: üç sayı soldan sağa sayısal olarak karşılaştırılır; eşitlik durumunda ön yayın etiketi olan sürüm olmayandan küçüktür; iki taraf da ön yayınsa etiket parçaları tek tek karşılaştırılır.
// dosya: surum.mjs // Sürüm dizesini çekirdek üçlü ve ön yayın parçalarına ayırır; yapı üstverisi atılır. export function ayristir(dizge) { const [surumsuz] = dizge.split('+'); const [cekirdek, onYayin = ''] = surumsuz.split('-', 2); const [buyuk, kucuk, yama] = cekirdek.split('.').map(Number); return { buyuk, kucuk, yama, onYayin: onYayin === '' ? [] : onYayin.split('.'), }; } function parcaKarsilastir(a, b) { const aSayi = /^\d+$/.test(a); const bSayi = /^\d+$/.test(b); if (aSayi && bSayi) return Number(a) - Number(b); if (aSayi) return -1; // sayısal parça, harfli parçadan küçüktür if (bSayi) return 1; return a < b ? -1 : a > b ? 1 : 0; } export function karsilastir(x, y) { const a = ayristir(x); const b = ayristir(y); for (const alan of ['buyuk', 'kucuk', 'yama']) { if (a[alan] !== b[alan]) return a[alan] - b[alan]; } if (a.onYayin.length === 0 && b.onYayin.length > 0) return 1; if (a.onYayin.length > 0 && b.onYayin.length === 0) return -1; const uzunluk = Math.min(a.onYayin.length, b.onYayin.length); for (let i = 0; i < uzunluk; i += 1) { const sonuc = parcaKarsilastir(a.onYayin[i], b.onYayin[i]); if (sonuc !== 0) return sonuc; } return a.onYayin.length - b.onYayin.length; }
// dosya: sirala.mjs — surum.mjs yukarıdaki dosyadır import { karsilastir } from './surum.mjs'; const surumler = ['1.10.0', '1.9.0', '2.0.0-rc.2', '2.0.0-rc.10', '2.0.0', '2.0.0-alpha', '1.9.0+derleme.7']; console.log(surumler.sort(karsilastir).join(' < '));
$ node sirala.mjs 1.9.0 < 1.9.0+derleme.7 < 1.10.0 < 2.0.0-alpha < 2.0.0-rc.2 < 2.0.0-rc.10 < 2.0.0
Çıktıdaki dört gözlem kuralın tamamını özetler:
1.10.0 sürümü 1.9.0 sürümünden sonra geldi — parçalar sayı olarak karşılaştırıldı.
2.0.0-alpha sürümü 2.0.0 sürümünden önce geldi. Ön yayın etiketi taşıyan bir sürüm,
aynı çekirdek üçlüye sahip etiketsiz sürümden küçüktür. Etiket, “bu sürüm henüz o sürüm
değil” anlamına gelir.
2.0.0-rc.2 sürümü 2.0.0-rc.10 sürümünden önce geldi. Etiketin sayısal parçaları sayı
olarak karşılaştırılır; metin sırası kullanılsaydı sıra ters çıkardı.
1.9.0 ile 1.9.0+derleme.7 karşılaştırmada eşit çıktı: artı işaretinden sonrası
yapı üstverisidir (build metadata) ve önceliğe girmez. Sıralama kararlı olduğu için
ikisi, listedeki asıl sıralarını korudu. Eşitlik, iki farklı arşifin aynı sürüm sayılması
demektir; bu yüzden yapı üstverisi yayımlanan sürümleri ayırmakta kullanılamaz.
Sürüm Aralıkları
Bildirimdeki aralık, kabul edilen sürüm kümesini belirtir. En sık kullanılan iki işaret, sözleşmenin iki farklı düzeyine güvenir: biri ana sürümün, diğeri ikincil sürümün sabit kalmasını ister.
// dosya: aralik.mjs — surum.mjs bir önceki bölümdeki dosyadır import { ayristir, karsilastir } from './surum.mjs'; // '^' ve '~' aralıklarını [alt, ust) çiftine çevirir. export function sinirlar(aralik) { const isaret = aralik[0] === '^' || aralik[0] === '~' ? aralik[0] : '='; const taban = isaret === '=' ? aralik : aralik.slice(1); const { buyuk, kucuk, yama } = ayristir(taban); if (isaret === '=') return [taban, `${buyuk}.${kucuk}.${yama + 1}`]; if (isaret === '~') return [taban, `${buyuk}.${kucuk + 1}.0`]; if (buyuk > 0) return [taban, `${buyuk + 1}.0.0`]; if (kucuk > 0) return [taban, `0.${kucuk + 1}.0`]; return [taban, `0.0.${yama + 1}`]; } export function uyar(surum, aralik) { const [alt, ust] = sinirlar(aralik); const onYayinli = ayristir(surum).onYayin.length > 0; const tabanOnYayinli = ayristir(alt).onYayin.length > 0; if (onYayinli && !tabanOnYayinli) return false; return karsilastir(surum, alt) >= 0 && karsilastir(surum, ust) < 0; }
// dosya: deneme.mjs — aralik.mjs yukarıdaki dosyadır import { sinirlar, uyar } from './aralik.mjs'; for (const aralik of ['^1.4.0', '~1.4.0', '^0.4.2', '^0.0.3', '1.4.0']) { const [alt, ust] = sinirlar(aralik); console.log(`${aralik.padEnd(8)} => ${alt} <= s < ${ust}`); } console.log(''); const denemeler = [ ['1.4.9', '^1.4.0'], ['1.9.0', '^1.4.0'], ['2.0.0', '^1.4.0'], ['1.5.0', '~1.4.0'], ['0.4.9', '^0.4.2'], ['0.5.0', '^0.4.2'], ['2.0.0-rc.1', '^1.4.0'], ]; for (const [surum, aralik] of denemeler) { console.log(`${surum.padEnd(11)} ${aralik.padEnd(8)} ${uyar(surum, aralik) ? 'uyar' : 'uymaz'}`); }
$ node deneme.mjs ^1.4.0 => 1.4.0 <= s < 2.0.0 ~1.4.0 => 1.4.0 <= s < 1.5.0 ^0.4.2 => 0.4.2 <= s < 0.5.0 ^0.0.3 => 0.0.3 <= s < 0.0.4 1.4.0 => 1.4.0 <= s < 1.4.1 1.4.9 ^1.4.0 uyar 1.9.0 ^1.4.0 uyar 2.0.0 ^1.4.0 uymaz 1.5.0 ~1.4.0 uymaz 0.4.9 ^0.4.2 uyar 0.5.0 ^0.4.2 uymaz 2.0.0-rc.1 ^1.4.0 uymaz
Şapka işareti “ana sürüm değişmesin” der ve ikincil yükseltmeleri kabul eder. Tilde işareti “ikincil sürüm de değişmesin” der ve yalnızca yama yükseltmelerini kabul eder. İşaretsiz yazım tek bir sürümü sabitler.
Üçüncü ve dördüncü satırlar özel bir kuralı gösterir. Ana sürümü sıfır olan paketler kararlı sayılmaz; sözleşme henüz yürürlükte değildir. Bu yüzden şapka işareti sıfırıncı ana sürümde bir basamak sağa kayar ve ikincil sürümü sabit tutar. Sıfırıncı ikincil sürümde bir basamak daha kayar. Bu kayma, kararsız paketlerin sessiz yükseltmelerle projeye sızmamasını sağlar.
Son satır ön yayınlarla ilgili kuralı gösterir: 2.0.0-rc.1 sürümü sayısal olarak
2.0.0 sürümünden küçük olsa da ^1.4.0 aralığına girmez. Ön yayınlar, aralığın tabanı
zaten bir ön yayın değilse hiçbir aralığa dahil edilmez. Aksi hâlde kararlı bir aralık,
yayımlanmamış sayılan sürümleri çekerdi.
Sözleşmenin Sınırları
Aralık işaretlerinin verdiği rahatlık — ikincil yükseltmeleri otomatik almak — bir varsayıma dayanır: yayıncının ikincil sürüm sözünü tutması. Bu varsayım üç yerde zayıflar.
Kırıcı değişikliğin tanımı belirsizdir. Belgelenmemiş bir davranışa bağlanan tüketici, o davranış değişince kırılır; yayıncı ise açık arayüzü değiştirmediğini düşünür. Belgelenmiş davranışın sınırı yazılı değilse anlaşmazlık kaçınılmazdır.
Hata düzeltmesi de kırabilir. Yanlış bir sonucu düzelten yama sürümü, o yanlış sonuca göre yazılmış kodu bozar. Sözleşme bunu yama sayar.
İnsan hatası vardır. Yanlış parça artırılabilir; kırıcı bir değişiklik ikincil sürüm olarak yayımlanabilir.
Bu üç nedenle aralıklar bir güvence değil, bir kolaylıktır. Yeniden üretilebilirliği veren şey aralık değil, önceki derste tanıtılan kilit dosyasıdır. Aralık “hangi yükseltmeleri kabul etmeye hazırım” der; kilit dosyası kurulu ağacın tam hâlini kaydeder. İkisi birlikte çalışır: yükseltme kararı bilinçli bir işlemle alınır, kilit dosyası güncellenir, sınamalar çalıştırılır.
Yayıncı tarafında sözleşmeyi işler kılan araç, kullanımdan kaldırma (deprecation) adımıdır: kaldırılacak bir yetenek önce ikincil bir sürümde uyarı verecek biçimde işaretlenir, yerine geçecek yol belgelenir, ancak bir sonraki ana sürümde kaldırılır. Bu adım atlanırsa ana sürüm numarası yükseltmeyi bildirir ama geçişi mümkün kılmaz.
Özet
- Sürüm numarasının üç parçası bir söz taşır: yama ve ikincil yükseltmeler kodun değişmeden çalışacağını, ana yükseltme değişebileceğini bildirir.
- Sözün konusu paketin açık arayüzüdür; kapsüllenmiş iç dosyalardaki değişiklik ana sürümü artırmaz.
- Karşılaştırma parçalar üzerinden sayısal yapılır; ön yayın etiketli sürüm etiketsizden küçüktür, yapı üstverisi önceliğe girmez.
- Şapka işareti ana sürümü, tilde işareti ikincil sürümü sabit tutar; ana sürümü sıfır olan paketlerde bu sınırlar bir basamak sağa kayar.
- Aralıklar kolaylıktır, güvence değil: kırıcı değişikliğin tanımı belirsizdir, yama düzeltmeleri de kırabilir. Yeniden üretilebilirliği kilit dosyası verir.
Sonraki Adım
Şimdiye kadar tek bir paketin tek bir aralığı incelendi. Gerçek bir projede onlarca paket, aynı bağımlılığı farklı aralıklarla ister ve bu istekler her zaman kesişmez. Sonraki ders, kesişmediğinde ne olduğunu gösterir: ağaçta yinelenen sürümler, aynı kütüphanenin iki ayrı örneği ve bu ikiliğin ürettiği kimlik sorunları.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.