İçeriğe geç
academia.sh

Ders 02 / 09

Bağımlılık Yönetimi

Kilit dosyası aynıyken kurulan sürüm neden değişmez? Ders sürüm kısıtlarının hangi sürümleri kabul ettiğini, kilitsiz çözümün depoya göre değiştiğini ve bağımlılığın belirleyen olduğunu ölçer.

Yayın Güncelleme

Bu derste

  • Karamsar kısıtın, aralık kısıtının ve tam sürüm kısıtının hangi sürümleri kabul ettiğini ölçer
  • Kilitsiz çözümün deponun içeriğine, kilitli çözümün kilit dosyasına bağlı olduğunu gösterir
  • Kilit dosyasındaki sürüm değişince kurulan sürümün değiştiğini ve bunun bağımlılık belirleyeni olduğunu ayırır
  • Kuralın kısıtı karşılamayan bir kilit karşısında sınıflandırma yapmadan hata verdiğini ölçer
İçindekiler

Önceki ders paketin dosya adının ad ve sürümden geldiğini gösterdi: o sonuç koddu. Paket tanımında ayrıca bir bağımlılık yazılıydı, ama hangi sürümünün kurulacağı yazılı değildi. Bu dersin sorusu bu yüzden bir adım ileridedir: kilit dosyası aynı kaldığı sürece kurulan sürüm neden değişmiyor, ve kilit değişince ne oluyor?

Kursun sorusu aynıdır: bu sonucu ne belirliyor? Bu dersin gözlemi kilitli surum satırıdır. M12/K02’de “bu değişiklik nereye ulaşıyor” sorulmuştu; orada bir değişikliğin hangi katmanlara yayıldığı ölçüldü. Burada soru tersine döner: kurulan sürüm hangi girdiye bağlı? Çekirdeğin belirleyen.rb dosyası birebir yeniden verilir; blok kaynaktır ve require_relative ile çekilir.

# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end

Sürüm Kısıtı

Bağımlılık yöneticisi, bir paketin hangi sürümlerle çalışacağını sürüm kısıtı üzerinden çözer. Kısıt bir sürüm değil, bir kabul aralığıdır. Üç biçim görülür. Tam sürüm kısıtı tek bir sürümü kabul eder. Aralık kısıtı iki sınır arasını kabul eder: >= 1.4 ve < 3 birlikte yazılır. Üçüncüsü karamsar kısıttır (pessimistic constraint): ~> 1.4 yazısı, “1.4’ten küçük olmayan, ama ana sürümü değişmeyen” anlamına gelir. Kısıt, yazılan son basamağın yükselmesine izin verir, ondan önceki basamağın yükselmesine izin vermez; ~> 1.4 için üst sınır 2’dir.

Aşağıdaki dosya üç biçimi aynı sürüm listesine uygular. Gem::Requirement kısıtı kurar, satisfied_by? bir sürümün kabul edilip edilmediğini söyler.

# kisitlar.rb — surum kisitlari hangi surumleri kabul eder
def kabul_edilen(kisitlar, surumler)
  gerek = Gem::Requirement.new(*kisitlar)
  surumler.select { |s| gerek.satisfied_by?(Gem::Version.new(s)) }
end

liste = %w[1.3.9 1.4.0 1.4.2 1.5.0 1.99 2.0.0]
p kabul_edilen(["~> 1.4"], liste)
p kabul_edilen(["~> 1.4.0"], liste)
p kabul_edilen([">= 1.4", "< 3"], %w[1.3.9 1.4.0 2.9 3.0])

bagimlilik = Gem::Dependency.new("bicimleyici", "~> 1.4")
p bagimlilik.match?("bicimleyici", "1.9.0")
p bagimlilik.match?("bicimleyici", "2.0.0")
["1.4.0", "1.4.2", "1.5.0", "1.99"]
["1.4.0", "1.4.2"]
["1.4.0", "2.9"]
true
false

~> 1.4 ana sürümü 1’de tutar ve 1.99’a kadar her şeyi kabul eder: alt sürüm yükselebilir. ~> 1.4.0 bir basamak daha uzundur ve kabul aralığı daralır: yalnız 1.4.0 ile 1.4.2. Son basamak kısıtın genişliğini belirler. Aralık kısıtı iki sınırın kesişimidir ve 3.0’ı dışarıda bırakır. Gem::Dependency ise adı ve kısıtı birlikte taşır; 1.9.0’ı kabul eder, 2.0.0’ı reddeder. Anlamlı sürümleme sözleşmesi doğruysa, ana sürüm değişmediği sürece uyumsuz bir değişiklik gelmez; ama bu sözleşmeyi paketin yazarı verir, kısıt onu denetlemez. RK6. Bu ölçüm kısıtın hangi sürümleri kabul ettiğini gösterir; kabul edilen bir sürümün gerçekten uyumlu olduğunu göstermez.

Kilitsiz Çözüm

Kısıt birden çok sürümü kabul ettiğinde yöneticinin bir tanesini seçmesi gerekir. En basit kural en yüksek uygun sürümü almaktır. Aşağıdaki saf Ruby yardımcı bu çözümün küçük bir modelidir.

# cozum.rb — kilit yokken depodan en yuksek uygun surum
def en_yuksek_uygun(depo, kisit)
  gerek = Gem::Requirement.new(kisit)
  depo.map { |s| Gem::Version.new(s) }.select { |s| gerek.satisfied_by?(s) }.max.to_s
end

Yardımcı kaynaktır. Aşağıdaki dosya onu önce üç sürümlük bir depoya, sonra bir sürüm eklenmiş depoya uygular.

# depo.rb — ayni kisit, degisen depo
require_relative "cozum"

depo = %w[1.4.2 1.5.0 2.0.0]
p en_yuksek_uygun(depo, "~> 1.4")

depo << "1.6.0"
p en_yuksek_uygun(depo, "~> 1.4")
"1.5.0"
"1.6.0"

Kısıt aynı kaldı, depo değişti, sonuç "1.5.0"’dan "1.6.0"’a geçti. Kilitsiz çözüm deponun o anki içeriğine bağlıdır. Dün kurulan makine ile bugün kurulan makine aynı kısıtla farklı sürüm alabilir. Bu, “benim makinemde çalışıyordu” sorusunun kaynaklarından biridir.

Kilit Dosyası

Bunun çaresi, çözümün sonucunu bir kilit dosyasına yazmaktır. Kilit dosyası bildirim dosyasındaki kısıtlardan çözülmüş tam sürümleri tutar. Sonraki her kurulum çözüm yapmaz, kilitteki sürümü okur. Kilit dosyası depoya işlenir, çünkü bütün makinelerin aynı sürümleri alması ancak böyle sağlanır.

Çekirdekteki kurulan_surum bir bağımlılık çözücü değil, kilit dosyasındaki sürümü okuyan bir modeldir. Aşağıdaki dosya onun tanımını birebir alır.

# bagimlilik.rb — kilitteki surumu okuyan model
# Bagimlilik cozumu: kilitteki surum kisiti karsiliyorsa o surum kurulur.
def kurulan_surum(kosum, ad, kisit)
  surum = Gem::Version.new(kosum.kilit.fetch(ad))
  raise ArgumentError, "kilit kisiti karsilamiyor" unless Gem::Requirement.new(kisit).satisfied_by?(surum)

  surum.to_s
end

Model üç yerde ölçülür: tam sürüm kısıtında, geniş kısıtta ve kilidi değişmiş koşumda. Son satır kuralın bu gözlemi nasıl sınıflandırdığını gösterir.

# kilit.rb — kilit kurulan surumu sabitler
require_relative "belirleyen"
require_relative "bagimlilik"

p kurulan_surum(TABAN, "bicimleyici", "~> 1.4.0")
p kurulan_surum(TABAN, "bicimleyici", "~> 1.4")
p kurulan_surum(DEGISKENLER[:bagimlilik], "bicimleyici", "~> 1.4")
p belirleyen(->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4") })
"1.4.2"
"1.4.2"
"1.5.0"
["bagimlilik", "B+ S- O-", "1.4.2"]

Kısıt ~> 1.4 geniş olduğu hâlde, kilit 1.4.2 dediği için kurulan "1.4.2" kaldı; kısıt 1.5.0’ı kabul eder, ama kilit onu seçmedi. Kilit 1.5.0 olunca kurulan sürüm de "1.5.0" oldu. Dördüncü satır sınıflandırmayı verir: bagimlilik, kanıt B+ S- O-. Bağımlılık değişkeninde sonuç değişti, sıra ve ortam değişkenlerinde değişmedi. Kısıt sürümü sabitlemez, yalnız sınırlar; sabitleyen kilittir. Bu dersin belirleyeni bağımlılıktır ve ders boyunca bu kelime kilit dosyasının içeriği demektir. RK7. Ölçüm, kilit dosyasındaki sürüm listesinin çözümden geldiğini varsayar; bu listenin gerçekten bir çözücü tarafından üretildiği modele dahil değildir.

Kilidi Güncellemek

Kilit dosyası kendiliğinden değişmez. Depoya yeni sürüm gelmesi, kilidi yenilemek için yeterli değildir; kilit yalnız yönetici yeniden çözüm yaptığında yazılır. Bu bir açık işlemdir ve çekirdekte TABAN.with ile temsil edilir: kilit dosyasının içeriği yeni bir değerle değiştirilir, eski değer olduğu gibi kalır.

# guncelle.rb — kilit yalniz acikca guncellenince degisir
require_relative "belirleyen"
require_relative "bagimlilik"
require_relative "cozum"

depo = %w[1.4.2 1.5.0 1.6.0]
yeni = en_yuksek_uygun(depo, "~> 1.4")
guncel = TABAN.with(kilit: TABAN.kilit.merge("bicimleyici" => yeni))

p kurulan_surum(TABAN, "bicimleyici", "~> 1.4")
p kurulan_surum(guncel, "bicimleyici", "~> 1.4")
p TABAN.kilit
p guncel.kilit
"1.4.2"
"1.6.0"
{"bicimleyici" => "1.4.2", "ayristirici" => "2.0.1"}
{"bicimleyici" => "1.6.0", "ayristirici" => "2.0.1"}

Depoda 1.6.0 bulunmasına rağmen TABAN’ın kilidi 1.4.2 kaldı; kurulan_surum bu koşumda "1.4.2" verdi. Kilidi yenilenmiş koşum "1.6.0" verdi, ve yalnız bicimleyici değişti: ayristirici kilitte 2.0.1 olarak kaldı. Bir güncelleme yalnız istenen bağımlılığı oynatabilir, ötekilere dokunmaz. Bu, bağımlılığın neden bir belirleyen olduğunu da açıklar: sonucu değiştiren şey kod değildir, kilit dosyasına yapılan bir yazıdır. Kod ve kısıt aynı kalır; kilit değişince sonuç değişir. RK8. Güncelleme burada tek bir bağımlılık için ve saf bir depoyla modellendi; birbirine bağlı bağımlılıkların birlikte güncellenmesi ölçülmedi.

İki Paketin Kısıtı

Bir uygulama birden çok paket kurar ve her paket aynı bağımlılık için kendi kısıtını yazar. Yönetici bütün kısıtları birlikte karşılayan bir sürüm arar: kabul aralıklarının kesişimi. Kesişim boşsa çözüm yoktur. Aşağıdaki dosya kabul_edilen’i iki paketin kısıtıyla çağırır.

# kesisim.rb — iki paketin kisitinin kesisimi
def kabul_edilen(kisitlar, surumler)
  gerek = Gem::Requirement.new(*kisitlar)
  surumler.select { |s| gerek.satisfied_by?(Gem::Version.new(s)) }
end

depo = %w[1.4.0 1.4.2 1.5.0 1.99 2.0.0]
p kabul_edilen(["~> 1.4", ">= 1.5"], depo)
p kabul_edilen(["~> 1.4.0", ">= 1.5"], depo)
p kabul_edilen(["~> 1.4", "~> 2.0"], depo)

onsurum = Gem::Dependency.new("bicimleyici", "~> 1.4")
p Gem::Requirement.new("~> 1.4").satisfied_by?(Gem::Version.new("1.5.0.pre1"))
p onsurum.match?("bicimleyici", "1.5.0.pre1")
["1.5.0", "1.99"]
[]
[]
true
false

Birinci paket ~> 1.4, ikincisi >= 1.5 yazınca depodaki 1.5.0 ile 1.99 kalır. İkincisi ~> 1.4.0 yazarsa kesişim boşalır, çünkü 1.4.x aralığı ile 1.5 ve sonrası hiçbir sürümde buluşmaz. Üçüncü satırda iki karamsar kısıt ana sürümde ayrılır ve yine hiçbir sürüm kalmaz. Boş küme bir çözüm değildir: yönetici burada kurulum yapmaz, çakışmayı bildirir. Çözüm, bütün kısıtların kesişiminde kalan bir sürümü aramaktır. Bu, kilit dosyasının neyi saklamadığını da gösterir: kilit çözümün sonucunu saklar, ama çözümün var olması kısıtların kesişimine bağlıdır.

Son iki satır ön sürümü ayırır. Kısıt nesnesi 1.5.0.pre1’i yalnız sayılara bakarak kabul eder. Bağımlılık nesnesi ise ön sürümü varsayılan olarak aday saymaz. Aynı kısıt iki nesnede iki sonuç verdi, bu yüzden bir ön sürümün kurulup kurulmayacağı kısıta değil, soruyu soran nesneye bağlıdır. RK9. Bu dersin modeli yalnız Gem modülünün nesnelerini ölçer; gerçek bir yöneticinin ön sürümü hangi kuralla aday saydığı sınanmadı.

İki Makine, Bir Kilit Dosyası

Kilit dosyasının depoya işlenme gerekçesi iki makineyle ölçülür. Birinci makine kurulumu bugün yapar, ikincisi deponun bir sürüm daha kazandığı ertesi gün. Kilitsiz kurulum çözümü her makinede yeniden yapar. Kilitli kurulum çözüm yapmaz, kilidi okur.

# makineler.rb — kilitsiz ve kilitli kurulum iki makinede
require_relative "belirleyen"
require_relative "bagimlilik"
require_relative "cozum"

bugun = %w[1.4.2 1.5.0]
yarin = bugun + ["1.6.0"]

p [en_yuksek_uygun(bugun, "~> 1.4"), en_yuksek_uygun(yarin, "~> 1.4")]
p [kurulan_surum(TABAN, "bicimleyici", "~> 1.4"), kurulan_surum(TABAN, "bicimleyici", "~> 1.4")]
["1.5.0", "1.6.0"]
["1.4.2", "1.4.2"]

Kilitsiz çiftte iki makine iki farklı sürüm kurdu; kilitli çiftte ikisi de aynısını kurdu. İkinci çiftte depo hiç kullanılmadı: kurulan_surum depoya bakmaz, bu yüzden deponun ertesi günkü içeriği sonucu etkilemez. Dün ile bugün arasındaki fark deponun değişmesidir; kilit dosyası bu farkı kurulumdan ayırır. Tutarlılığı kilit dosyası sağlar, ve bu ancak herkes aynı kilit dosyasını okuyorsa doğrudur. Kilit dosyası yalnız bir makinede duruyorsa öteki makinenin kurduğu sürüm bu ölçüm hakkında hiçbir şey söylemez. RK10. Model iki makineyi aynı kilit nesnesiyle temsil eder; gerçek bir ekipte kilit dosyasının eşitlenmesi ayrı bir iştir ve burada ölçülmedi.

Bağımlılık Türü ve Kısıt

Çalışma ve geliştirme bağımlılığı ayrımı bir önceki derste paket tanımı üzerinden kuruldu. Burada eklenen, türün kısıtla ilişkisidir: tür kurulan sürümü değil, nerede kurulacağını belirler, ve iki tür de aynı biçimde bir kısıt taşır.

# tur.rb — bagimlilik turu ve kisit
bicimleyici = Gem::Dependency.new("bicimleyici", "~> 1.4", :runtime)
sinayici = Gem::Dependency.new("sinayici", ">= 2", :development)

p [bicimleyici.type, bicimleyici.runtime?]
p [sinayici.type, sinayici.runtime?]
p bicimleyici.requirement.to_s
p sinayici.requirement.to_s
[:runtime, true]
[:development, false]
"~> 1.4"
">= 2"

Geliştirme bağımlılığının type değeri :development döndü ve runtime? yanlış oldu; çalışma bağımlılığında :runtime ve doğru. İki kısıt metni de verildiği gibi geri okundu: "~> 1.4" ve ">= 2". Her bağımlılığın türü ayrı okunur ve runtime? yalnız çalışma türünde doğrudur. Kısıtın metni türden bağımsızdır. Bu ayrım, kilit dosyasının neden her ikisini de içerebildiğini açıklar: kilit bütün çözümü tutar, tür ise o çözümün hangi bölümünün kimin makinesine ineceğini söyler. RK11. type ve runtime? yalnız Gem::Dependency nesnesinin ne dediğini gösterir; bir yöneticinin geliştirme bağımlılığını kurulum sırasında atlaması bu ölçümün dışındadır.

Sınırlayıcı Ölçüm

Kural bir hesabı üç değişkende koşturur. Hesap herhangi birinde hata verirse sınıflandırma yapamaz. Bu dersin kendi hatası bunu gösterir: kısıt ~> 1.4.0 iken tabandaki kilit (1.4.2) kısıtı karşılar, ama bağımlılık değişkeninin kilidi (1.5.0) karşılamaz.

# sinir.rb — kilit kisiti karsilamayinca kural siniflandirmaz
require_relative "belirleyen"
require_relative "bagimlilik"

begin
  belirleyen(->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4.0") })
rescue => e
  p e.class
  puts e.message
end
ArgumentError
kilit kisiti karsilamiyor

Kural bir sınıf vermedi, ArgumentError yükseldi. Bu hata kuralın değil, hesabın hatasıdır: kilit dosyası kısıtla çelişiyor. Kurulumda görülen sorun budur; kural onu “bağımlılık” diye adlandırmaz çünkü hesap sonuç üretemedi. RK12. Bu ölçüm yalnız kilit ile kısıt çeliştiğinde olan şeyi gösterir; gerçek bir yönetici çelişkiyi kilidi yeniden çözerek gidermeyi seçebilir, bu modelin dışındadır.

Özet

  • Sürüm kısıtı bir sürüm değil bir kabul aralığıdır: ~> 1.4 ana sürümü sabit tutar, ~> 1.4.0 yalnız yama sürümünü serbest bırakır, aralık kısıtı iki sınırın kesişimidir.
  • Kilitsiz çözüm deponun içeriğine bağlıdır: depoya 1.6.0 gelince aynı kısıt başka sürüm verir.
  • Bu dersin belirleyeni bağımlılıktır: kilitli surum satırında B+ S- O- görünür ve kilit 1.5.0 olunca kurulan sürüm de değişir.
  • Kısıt sınırlar, kilit dosyası sabitler; kilit dosyası bu yüzden depoya işlenir.
  • Kilit kısıtı karşılamazsa kural sınıflandırma yapmaz, hata verir.

Sonraki Adım

Kurulan sürümü artık kilit dosyası belirliyor. Ama aynı kod ve aynı kilitle bile bir sonuç değişebilir. Bir sonraki ders, aynı testlerin neden bazen düştüğünü ve bunu sıranın nasıl belirlediğini ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat