Ders 16 / 18
Modüller ve Bağımlılıklar
Bir bağımlılığın sürümü kaynakta çoğu zaman hiç yazılmaz; altı istekten üçü ana modülün kendi kaynağında yazılı, üçü bir bağımlılığın kendi bağımlılığından türetilmiştir. Modül dosyasının yazılmayan sürümü doldurma kuralı — en düşük sürüm seçimi — kaynak içinde modellenerek koşturulur; hiçbir ağ çağrısı yapılmaz. Sınırlayıcı ölçüm, seçilen sürümün bir modülün kataloğundaki en yeni sürüm olmadığını, yalnız yazılan isteklerin en büyüğü olduğunu gösterir.
İçindekiler
Önceki ders bir paketin sınırının pakette durduğunu, dosyada durmadığını gösterdi. Bu ders aynı soruyu bir düzey yukarı taşıyor: bir paket başka bir pakete bağımlı olduğunda, o bağımlılığın hangi sürümü kastettiği kaynakta nerede yazılı? Çoğu zaman hiçbir yerde. Bir modül dosyası doğrudan bağımlı olduğu birkaç modülün sürümünü yazar; o modüllerin kendi bağımlılıklarının sürümü ise hiç yazılmaz, modüllerin kendi dosyalarından türer. Bu dersin sorusu kursun genel sorusunun aynısı: sürüm yazılmadığında modül dosyası onun yerine neyi koyuyor?
Bir bağımlılık grafiğinin sürüm çözmesi gereken bir sorunu var: aynı modül, grafikte birden fazla yoldan isteniyorsa, o modülün hangi sürümüyle derleneceği kaynağın hiçbir tek satırında yazmıyor. İki ayrı bağımlılık aynı üçüncü modülü farklı en düşük sürümlerle istediğinde, ana modül ikisi arasında seçim yapmak zorunda kalıyor — ama bu seçim ana modülün kaynağında görünmüyor, çünkü ana modül o üçüncü modülü hiç doğrudan anmıyor. Bu durumun adı bağımlılık grafiklerinde sık geçer: iki ayrı yol aynı üçüncü noktaya varıyor ve o noktada iki farklı istek çarpışıyor. Çarpışma bir hata değil — iki istek de “en az şu sürüm” diyor, ikisi de yalnız bir alt sınır çiziyor, üst sınır çizmiyor. Seçimin görevi bu iki alt sınırı tek bir sürümde birleştirmek, ve birleştirilen sürümün her iki isteği de karşıladığından emin olmak. Aşağıdaki ölçüm bu durumu en küçük hâliyle kuruyor ve seçim algoritmasını gerçekten çalıştırarak sonucu okuyor.
Altı İstek, İki Kaynak
Aşağıdaki ölçüm gerçek bir modül kaydına hiç bağlanmıyor; bağımlılık grafiği kaynak içinde
elle kuruluyor ve modellendiği açıkça burada yazılıyor. Dört modül var: kutuphaneA,
kutuphaneB, kutuphaneC, kutuphaneD. Ana modül kutuphaneA, kutuphaneB ve
kutuphaneD’yi doğrudan istiyor — üçünün sürümü de kaynakta yazılı. kutuphaneA,
kutuphaneC’nin bir sürümünü istiyor; kutuphaneB da kutuphaneC’nin başka bir sürümünü.
kutuphaneC da kendi payına kutuphaneD’nin bir sürümünü istiyor. Bu üç istek ana
modülün kaynağında hiç yazılı değil — ana modülün derlemesi sırasında, bağımlı olunan
modüllerin kendi dosyalarından türetiliyor.
// main.go — en dusuk surum secimi, bagimlilik grafigi kaynak icinde modelleniyor (ag cagrisi yok)
package main
import "fmt"
// Surum uc parcali bir surum numarasini tasir; bu ders gercek bir modul kaydina
// baglanmiyor, grafigin tamami asagida elle kuruluyor.
type Surum struct {
Ana, Ikincil, Yama int
}
func (s Surum) metin() string {
return fmt.Sprintf("%d.%d.%d", s.Ana, s.Ikincil, s.Yama)
}
// buyukMu s'nin digerinden daha yeni bir surum oldugunu soyler.
func (s Surum) buyukMu(diger Surum) bool {
if s.Ana != diger.Ana {
return s.Ana > diger.Ana
}
if s.Ikincil != diger.Ikincil {
return s.Ikincil > diger.Ikincil
}
return s.Yama > diger.Yama
}
// Istek, bir modulun bagimliliginin istedigi en dusuk surumu tasir. Yazili true ise
// istek dogrudan ana modulun kendi kaynaginda duruyor; degilse baska bir bagimliligin
// kendi modul dosyasindan turedi.
type Istek struct {
Modul string
Kimden string
Istenen Surum
Yazili bool
}
func main() {
istekler := []Istek{
{Modul: "kutuphaneA", Kimden: "ana modul", Istenen: Surum{1, 2, 0}, Yazili: true},
{Modul: "kutuphaneB", Kimden: "ana modul", Istenen: Surum{1, 1, 0}, Yazili: true},
{Modul: "kutuphaneD", Kimden: "ana modul", Istenen: Surum{2, 1, 0}, Yazili: true},
{Modul: "kutuphaneC", Kimden: "kutuphaneA", Istenen: Surum{1, 4, 0}, Yazili: false},
{Modul: "kutuphaneC", Kimden: "kutuphaneB", Istenen: Surum{1, 6, 0}, Yazili: false},
{Modul: "kutuphaneD", Kimden: "kutuphaneC", Istenen: Surum{2, 0, 0}, Yazili: false},
}
fmt.Println("-- alti istek, kaynagi yazili mi turetilmis mi --")
yaziliSayisi, turetilenSayisi := 0, 0
for _, ist := range istekler {
kaynak := "turetildi"
if ist.Yazili {
kaynak = "yazili"
yaziliSayisi++
} else {
turetilenSayisi++
}
fmt.Printf("%-12s <- %-12s %-9s istenen=%s\n", ist.Modul, ist.Kimden, kaynak, ist.Istenen.metin())
}
fmt.Printf("toplam: %d yazili, %d turetildi\n", yaziliSayisi, turetilenSayisi)
fmt.Println()
fmt.Println("-- secilen surum: her modulun tum isteklerinin en buyugu --")
siraliModuller := []string{"kutuphaneA", "kutuphaneB", "kutuphaneC", "kutuphaneD"}
secilen := map[string]Surum{}
for _, ist := range istekler {
mevcut, varMi := secilen[ist.Modul]
if !varMi || ist.Istenen.buyukMu(mevcut) {
secilen[ist.Modul] = ist.Istenen
}
}
for _, ad := range siraliModuller {
fmt.Printf("%-12s secilen=%s\n", ad, secilen[ad].metin())
}
fmt.Println()
fmt.Println("-- sinirlayici olcum: secim en yeniyi degil, yazilan en dusugu aliyor --")
kataloktakiEnYeniC := Surum{1, 9, 0}
fmt.Printf("kutuphaneC icin katalogtaki en yeni surum: %s\n", kataloktakiEnYeniC.metin())
fmt.Printf("secilen surum: %s\n", secilen["kutuphaneC"].metin())
fmt.Printf("secilen, katalogtaki en yeniyle ayni mi: %v\n", secilen["kutuphaneC"] == kataloktakiEnYeniC)
}
-- alti istek, kaynagi yazili mi turetilmis mi -- kutuphaneA <- ana modul yazili istenen=1.2.0 kutuphaneB <- ana modul yazili istenen=1.1.0 kutuphaneD <- ana modul yazili istenen=2.1.0 kutuphaneC <- kutuphaneA turetildi istenen=1.4.0 kutuphaneC <- kutuphaneB turetildi istenen=1.6.0 kutuphaneD <- kutuphaneC turetildi istenen=2.0.0 toplam: 3 yazili, 3 turetildi -- secilen surum: her modulun tum isteklerinin en buyugu -- kutuphaneA secilen=1.2.0 kutuphaneB secilen=1.1.0 kutuphaneC secilen=1.6.0 kutuphaneD secilen=2.1.0 -- sinirlayici olcum: secim en yeniyi degil, yazilan en dusugu aliyor -- kutuphaneC icin katalogtaki en yeni surum: 1.9.0 secilen surum: 1.6.0 secilen, katalogtaki en yeniyle ayni mi: false
PK8. Altı istekten üçü kaynakta yazılı, üçü türetilmiştir; hangi üçünün yazılı olduğu,
o modülün ana modülün doğrudan bağımlılığı olup olmamasına bağlıdır. PK9.
kutuphaneC’nin iki ayrı isteği var — biri kutuphaneA’dan, biri kutuphaneB’den — ve
ikisi de kaynakta hiç görünmeden, yalnız bu iki modülün kendi dosyalarından türüyor.
En Düşük Sürüm Seçimi
PK10. Bir modülün seçilen sürümü, o modül için yazılan bütün isteklerin en büyüğüdür;
kutuphaneC için hem 1.4.0 hem 1.6.0 istendiğinde seçilen 1.6.0 oluyor, kutuphaneD
için hem 2.0.0 hem 2.1.0 istendiğinde seçilen 2.1.0 oluyor. Kuralın adı en düşük
sürüm seçimi, ama seçilen sayı isteklerin en küçüğü değil en büyüğü — çelişki gibi
görünen bu adlandırma, kuralın neyi garanti ettiğine bakıldığında çözülüyor. Kural şunu
söylüyor: seçilecek sürüm, hiçbir isteği ihlal etmeyen en düşük sürüm olsun. 1.4.0
isteyen bir bağımlılık 1.6.0 ile de çalışır (sürümler geriye uyumlu kabul edilir), ama
1.6.0 isteyen bir bağımlılık 1.4.0 ile çalışacağının garantisini vermez. Bu yüzden
bütün isteklerin hepsini karşılayan en düşük sürüm, isteklerin en büyüğüdür — “en
düşük” sözcüğü tek bir isteğe değil, bütün istekleri aynı anda karşılayan noktaya işaret
ediyor.
Bu, geriye uyumluluğun bir varsayım olarak alındığı bir hesap; kural kendisi bunu
denetlemiyor, yalnız kabul ediyor. Yukarıdaki buyukMu karşılaştırması üç sayıyı sırayla
kıyaslıyor ve hiçbir yerde iki sürümün gerçekten uyumlu olup olmadığını sınamıyor —
uyumluluk, sürüm numarasının kendisine bağlanmış bir söz olarak kabul ediliyor, ayrıca
doğrulanmıyor. Seçim algoritması bu sözün tutulduğu bir dünyada doğru sonuç veriyor;
söz tutulmadığında (yeni sürüm eskisiyle aynı arayüzü sunmadığında) algoritma hâlâ aynı
sayıyı seçiyor, ama seçilen sürümün gerçekten çalışıp çalışmadığı ayrı bir sorudur — bu
sorunun yanıtı modül dosyasında değil, kaynağın kendisinin derlenip derlenmediğinde
bulunuyor.
Bu hesap kutuphaneD için iki katmanlı bir zincir üzerinden de çalışıyor: kutuphaneD
doğrudan ana modülden bir istek alıyor (2.1.0, yazılı) ve dolaylı olarak
kutuphaneC üzerinden bir istek daha alıyor (2.0.0, türetilmiş). Seçim bu ikisi arasında
hiçbir ayrım yapmıyor — istek nereden geldiğine değil, hangi sayıyı taşıdığına göre
karşılaştırılıyor, ve büyük olan (2.1.0) kazanıyor. Bir isteğin yazılı ya da türetilmiş
olması, seçim algoritmasının girdisi değil; yalnız bu dersin ölçtüğü, isteğin kaynağını
sınıflandıran ayrı bir gözlem.
Bu ayrımın önemi isteklerin sayısı arttıkça büyüyor. Yukarıdaki ölçümde dört modül ve altı
istek var; gerçek bir bağımlılık grafiğinde bu sayı yüzlerle ölçülebilir ve aynı modülün
onlarca farklı yoldan istenmesi sıradan bir durumdur. Seçim kuralı bu ölçekte de
değişmiyor: her modül için, o modüle giden bütün yolların istediği sürümlerin en büyüğü
alınıyor. Algoritmanın kendisi grafiğin derinliğine duyarsız — kutuphaneD’ye giden istek
kutuphaneC üzerinden iki basamak uzaklıktan gelse de, ana modülden doğrudan gelse de,
karşılaştırma aynı iki sayıyı yan yana koyup büyüğünü seçmekten ibaret. Karmaşıklık
grafiğin büyüklüğünde duruyor, kuralın kendisinde değil.
Sınırlayıcı Ölçüm: En Yeni Değil, Yazılan En Düşük
Yukarıdaki koşum bir katalog değeri daha taşıyor: kutuphaneC için “mevcut en yeni sürüm”
1.9.0 olarak veriliyor, ama seçilen sürüm 1.6.0. PK11. Seçim bir modülün elinde
bulunan en yeni sürümü hiç sormuyor; yalnız gerçekten yazılan (ya da türetilen) isteklere
bakıyor ve onların en büyüğünü alıyor. Bu, en düşük sürüm seçiminin en çok yanlış anlaşılan
tarafı: kural bir bağımlılığı güncel tutmuyor, yalnız kaynakta bir yerde (doğrudan ya
da dolaylı) istenmiş olanı karşılıyor. kutuphaneC’nin 1.9.0 sürümü var olabilir, ama
hiçbir istek onu istemediği sürece seçilen sürüme hiç girmiyor.
Bunun bilinçli bir tercih olduğu, neyi güvence altına aldığına bakılınca görülüyor: bir bağımlılık grafiğinin sonucu, o grafiği kimin, ne zaman derlediğinden bağımsız olsun isteniyor. Eğer seçim “her zaman en yeniyi al” olsaydı, aynı kaynak metin farklı zamanlarda derlendiğinde farklı bir bağımlılık kümesiyle derlenebilirdi — üstelik kaynağın kendisi hiç değişmeden. En düşük sürüm seçimi bunun yerine yalnız kaynakta (dolaylı olarak da olsa) yazılana bakıyor; aynı istek kümesi, ne zaman ve nerede çalıştırılırsa çalıştırılsın, aynı sürüm kümesini seçiyor. Bedeli açık: bir bağımlılığın yeni bir sürümünden yararlanmak için o sürümü isteyen bir satırın bir yerde yazılması gerekiyor; hiçbir istek kendiliğinden yükselmiyor.
Bunun bir sonucu daha var: iki ayrı geliştirici, aynı bağımlılık grafiğini aynı anda, birbirinden habersiz çalıştırsa bile aynı sürüm kümesine varır — çünkü seçim hiçbir dış duruma (kataloğun o anki içeriğine, hangi sürümlerin “en yeni” sayıldığına) bakmıyor, yalnız kaynakta yazılana bakıyor. Bir bağımlılığı yükseltmek istendiğinde bu yüzden ayrı ve bilinçli bir adım gerekiyor: ya doğrudan istenen sürüm kaynakta değiştiriliyor, ya da zincirdeki bir ara bağımlılığın kendi isteği yükseltiliyor. Her iki durumda da yükselme, bir yerde yeni bir sayının yazılmasıyla oluyor; yazılmadığı sürece seçim hep aynı sonuca dönüyor, çünkü hesabın kendisi belirlenimci.
Yazılı, Türetilmiş, Seçilmiş
Bir modül dosyasının gerçek kaynağında bu üç kavram üç ayrı satıra dağılır. Ana modülün
doğrudan istediği sürümler modül dosyasının kendi bildirim bloğunda, açıkça yazılı durur.
Yalnızca türetilmiş bir sürümü taşıyan modüller de aynı bloğa düşer, ama bu satırlar
“dolaylı” olarak işaretlenir — kaynağın hiçbir yerinde import edilmeyen, yalnız seçim
hesabının bir girdisi olan modülleri gösterirler. Seçilen sürüm ise bu iki kümenin
birleşiminden, yukarıdaki hesapla çıkar; modül dosyası bu üçünü ayrı ayrı taşıdığı için bir
modülün neden o sürümde durduğu — kendi isteği mi yoksa başka bir bağımlılığın isteği mi —
kaynaktan geriye doğru izlenebilir kalır.
Gerçek bir modül dosyasında bu bildirim bloğu satır satır okunabilir: her satır bir modül
adı ve o modül için seçilmiş sürümü taşır, ve doğrudan istenmeyip yalnız türetilmiş olan
bir modülün satırının sonuna bunu belirten kısa bir not eklenir. Bu notun kendisi bir sürüm
bilgisi değil, bir köken bilgisidir — o modülün ana modülün kaynağında hiç
import edilmediğini, yalnız seçim hesabına başka bir modül üzerinden girdiğini söyler.
Yukarıdaki ölçümdeki Yazili alanı tam olarak bu notun karşılığı: kutuphaneA,
kutuphaneB, kutuphaneD için Yazili alanı true (bunlar ana modülün kendi kaynağında
import ediliyor), kutuphaneC’nin iki isteği için false (hiçbiri ana modülün
kaynağında hiç anılmıyor). Bildirim bloğunun kendisi seçim hesabından sonra yazılır —
önce bütün istekler toplanır, en düşük sürüm seçimi çalıştırılır, sonra yalnız sonuç
(seçilen sürüm) bloğa düşer. Ara adımlar — hangi isteğin hangi modülden geldiği — modül
dosyasının kendisinde hiç görünmez; bu bilgi yalnız seçim hesabı çalışırken bir an için
vardır, tıpkı yukarıdaki ölçümün istekler diliminde durduğu gibi.
Bu üç ayrımın (yazılı, türetilmiş, seçilmiş) pratik bir sonucu var: bir modül dosyasını okuyan biri, bir bağımlılığın neden orada durduğunu iki farklı soruyla ayırt edebiliyor. “Bu modülü ben mi istedim?” sorusunun yanıtı doğrudan/dolaylı ayrımında duruyor. “Hangi sürümü aldık?” sorusunun yanıtı ise seçilen sürüm satırında duruyor — ama bu ikinci soru kaynağın hiçbir satırından tek başına çıkmıyor, çünkü seçilen sürüm birden fazla isteğin karşılaştırılmasının sonucu. Modül dosyası bu yüzden bir bildirim listesinden çok bir hesabın son hâli: her satır, kaynakta görünmeyen bir karşılaştırmanın kazananını taşıyor.
Bağımlılık sürümünün nereden geldiğini bildirim satırından okumak (M06/K04 Modüller, Araçlar ve Ekosistem) ve bir paketin hangi sürümle yayımlanacağını belirlemek (M07/K05 Python Projeleri: Paketleme ve Test) başka dillerin kendi düzeneğinde daha önce kuruldu; bu ders o modeli tekrarlamıyor, yalnız Go’nun kendi seçim kuralını — isteklerin en büyüğünü almayı — ölçüyor.
Özet
- Bir bağımlılık grafiğindeki istekler iki kaynaktan gelir: ana modülün kendi kaynağında yazılı olanlar ve bir bağımlılığın kendi modül dosyasından türeyenler.
- En düşük sürüm seçimi, bir modül için yazılan bütün isteklerin en büyüğünü seçer; bu, hiçbir isteği ihlal etmeyen en düşük ortak nokta olduğu için “en düşük” adını taşır.
- İsteğin yazılı ya da türetilmiş olması seçim hesabını etkilemez; yalnız hangi sayının taşındığı karşılaştırılır.
- Sınırlayıcı ölçüm: seçilen sürüm bir modülün kataloğundaki en yeni sürüm değildir, yalnız gerçekten istenmiş olanların en büyüğüdür — hiçbir istek kendiliğinden yükselmez.
- Bu ölçüm gerçek bir ağ çağrısı yapmadan, bağımlılık grafiğini kaynak içinde modelleyerek koşturuldu.
Sonraki Adım
Bu derste bir modülün sürümünü kimin seçtiği ölçüldü: seçimi ne bir kişi ne ayrı bir araç belirliyor, isteklerin kendisi belirliyor — kaynakta yazılı olsun ya da bir bağımlılıktan türesin, hesap ikisine de aynı kuralı uyguluyor. Bir modülün sürümü bu yüzden kaynağın dışında, seçim algoritmasının bir çıktısı olarak duruyor. Ama bir modülün içindeki kaynağın biçimi ve uyduğu kalıp için aynı sorunun yanıtı farklı bir yerde: onları uygulayan bir kişi ya da bir kural kitabı değil, Go’nun kendi komut ailesi. Sıradaki ders bu komutları program içinden çağırıp, yazılmayan bir biçim kuralının kaynağı nasıl değiştirdiğini ve derleyicinin reddetmediği bir kalıbı hangi aracın yakaladığını ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.