İçeriğe geç
academia.sh

Ders 07 / 14

Bağımlılık Ağacı ve Çakışmalar

Kesişmeyen sürüm isteklerinin iç içe kurulumla çözülmesi, yinelenen kopyaların ürettiği kimlik ve durum sorunları, ağacın düzleştirilmesi ve tekillik gerektiren bağımlılıklar.

İçindekiler

Önceki ders tek bir aralığı inceledi. Bir projede onlarca paket bulunur ve bunların çoğu kendi bağımlılıklarını taşır. Aynı pakete iki farklı yerden, iki farklı aralıkla istek gelmesi olağandır.

İstekler kesişiyorsa sorun yoktur: kesişimden bir sürüm seçilir ve herkes onu kullanır. Kesişmediğinde ne olur? Bu dersin konusu, o durumun çözümü ve çözümün bedelidir.

Kesişmeyen İstekler

Ölçüm projesi ortak adlı bir paketin ikinci ana sürümünü ister. Projenin kullandığı ceviri paketi ise aynı paketin birinci ana sürümüne bağlıdır. ^2.0.0 ile ^1.0.0 kümelerinin kesişimi boştur.

Sistem paket yöneticilerinde bu durum, Linux’a Giriş kursundaki paket yöneticisi kavramı dersinde anlatıldığı gibi bir çakışma bildirimiyle sonuçlanır: bir kütüphanenin sistemde tek bir sürümü kurulabilir. Modül çözümlemesi ise konuma bağlı olduğu için başka bir çıkış yolu sunar — aynı paketin iki sürümü, ağacın iki ayrı düzeyinde durabilir.

İki bildirim, çakışan istekleri yazılı hâlde gösterir:

{
  "name": "proje", "version": "1.0.0", "type": "module",
  "dependencies": { "ceviri": "^3.1.0", "ortak": "^2.0.0" }
}
{
  "name": "ceviri", "version": "3.1.0", "type": "module", "main": "index.mjs",
  "dependencies": { "ortak": "^1.0.0" }
}

Kurulmuş ağacın biçimi şudur:

$ node agac.mjs
[email protected]
  [email protected]
[email protected]
yinelenen: ortak -> 1.0.0, 2.0.0

Bu çıktıyı üreten betik, önceki derste anlatılan arama kuralını tersinden uygular: her node_modules dizinini gezer ve bulduğu bildirimleri yazar.

// dosya: agac.mjs
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';

async function dolas(dizin, derinlik = 0, bulgular = new Map()) {
  const modulDizini = join(dizin, 'node_modules');
  let girdiler;
  try {
    girdiler = await readdir(modulDizini);
  } catch {
    return bulgular;
  }
  for (const ad of girdiler.sort()) {
    const paketDizini = join(modulDizini, ad);
    const bildirim = JSON.parse(await readFile(join(paketDizini, 'package.json'), 'utf8'));
    console.log(`${'  '.repeat(derinlik)}${bildirim.name}@${bildirim.version}`);
    bulgular.set(bildirim.name, (bulgular.get(bildirim.name) ?? new Set()).add(bildirim.version));
    await dolas(paketDizini, derinlik + 1, bulgular);
  }
  return bulgular;
}

const bulgular = await dolas('.');
for (const [ad, surumler] of bulgular) {
  if (surumler.size > 1) console.log(`yinelenen: ${ad} -> ${[...surumler].join(', ')}`);
}

Ağaçtaki yerleşim şudur: ortak paketinin ikinci sürümü kök dizinde, birinci sürümü ceviri paketinin kendi node_modules dizinindedir. Arama kuralı gereği ceviri içinden yapılan bir içe aktarma önce kendi dizinine bakar ve birinci sürümü bulur; kökten yapılan bir içe aktarma ikinci sürümü bulur. İki istek de karşılanmıştır.

İki Kopyanın Bedeli

Çakışma çözüldü, ama sonuç iki ayrı paket örneğidir. Modül sistemleri konusunda kurulan tek değerlendirme güvencesi paket başına değil, çözülmüş dosya yolu başına geçerlidir; iki farklı dosya, iki farklı modül demektir.

// dosya: node_modules/ortak/index.mjs — ikinci sürüm
export const surum = '2.0.0';
export class Kayit {
  constructor(ad) { this.ad = ad; }
}
const kayitlar = [];
export function ekle(kayit) { kayitlar.push(kayit); return kayitlar.length; }
// dosya: node_modules/ceviri/index.mjs — kendi dizinindeki birinci sürümü kullanır
import { ekle, Kayit, surum } from 'ortak';

export function kullan(ad) {
  const kayit = new Kayit(ad);
  return { gorulenSurum: surum, kayitSayisi: ekle(kayit), kayit };
}
// dosya: ana.mjs — yukarıdaki iki paketi kullanır
import { ekle, Kayit, surum } from 'ortak';
import { kullan } from 'ceviri';

const bizim = new Kayit('kok');
console.log('kokun gordugu surum:', surum, '| kayit sayisi:', ekle(bizim));

const onunki = kullan('ceviri-ici');
console.log('cevirinin gordugu surum:', onunki.gorulenSurum, '| kayit sayisi:', onunki.kayitSayisi);
console.log('cevirinin urettigi kayit bizim sinifimizdan mi:', onunki.kayit instanceof Kayit);
$ node ana.mjs
kokun gordugu surum: 2.0.0 | kayit sayisi: 1
cevirinin gordugu surum: 1.0.0 | kayit sayisi: 1
cevirinin urettigi kayit bizim sinifimizdan mi: false

Üç satır, üç ayrı sorunu gösterir.

Sürüm görünürlüğü kullanıcıya bağlıdır. Aynı programda iki farklı ortak sürümü çalışıyor. Hangisinin çalıştığı, sorunun nereden sorulduğuna göre değişir.

Durum paylaşılmaz. İki kayıt listesi ayrıdır; her ikisi de bir eleman içerir. Paketin tuttuğu kayıt, önbellek veya sayaç türünden her şey bölünmüştür. Bir kütüphane “tüm kayıtları listele” gibi bir işlev sunuyorsa, yalnızca kendi kopyasının kayıtlarını görür.

Kimlik denetimi başarısızdır. ceviri paketinin ürettiği nesne, kökteki sınıfın örneği değildir. Sınıflar aynı kaynaktan gelse bile iki ayrı değerlendirmenin ürünüdür; nesne kimliği bunu bilmez. Aynı sorun sembollerde, hata sınıflarında ve marka denetimlerinde ortaya çıkar.

Bu üç sorun, “programım çalışıyor ama bir yerde tuhaf davranıyor” biçimindeki tanısı zor arızaların sık nedenidir. Bağımlılık ağacında yinelenen sürüm aramak, bu tür bir arızada ilk adımdır.

Düzleştirme

İç içe kurulum her zaman gerekli değildir. İstekler kesişiyorsa tek bir kopya hepsini karşılar ve bu kopya ağacın köküne konur. Bu yerleştirmeye düzleştirme (flattening) denir.

Düzleştirmenin iki gerekçesi vardır. Birincisi yer ve süre: aynı paketin onlarca kopyası yerine tek kopya kurulur. İkincisi az önce sayılan üç sorunun önlenmesidir.

Düzleştirmenin bir yan etkisi, önceki derste değinilen sinsi duruma zemin hazırlamasıdır. Kök dizine çıkarılan bir paket, onu bildiriminde yazmayan modüllerden de görünür hâle gelir. Kod yerelde çalışır; ağaç başka bir biçimde kurulduğunda — örneğin farklı bir kurulum aracı iç içe yerleşimi yeğlediğinde — paket kaybolur. Bu yüzden kural açıktır: kullanılan her paket, kullanan paketin bildiriminde yazılı olmalıdır.

Tekillik Gerektiren Bağımlılıklar

Bazı paketler için iki kopya yalnızca israf değil, doğrudan hatadır. Kayıt tutan, genel bir durum yöneten ya da nesne kimliğine dayanan kütüphaneler tek örnek olmak zorundadır.

Bunu sağlayan mekanizma, ilk derste tanıtılan eş bağımlılık listesidir. Bir eklenti, genişlettiği kütüphaneyi çalışma bağımlılığı olarak yazarsa kendi dizinine kendi kopyasını kurar ve ikilik oluşur. Eş bağımlılık olarak yazdığında ise kütüphaneyi kurmaz; yalnızca “şu aralıktaki bir sürüm ortamda bulunmalı” der. Kurma sorumluluğu uygulamaya geçer ve ağaçta tek kopya kalır.

Eş bağımlılık aralığı bilinçli seçilmelidir. Fazla dar bir aralık, eklentinin kullanılabildiği sürüm kümesini gereksiz daraltır ve kesişmeyen istekler üretir; fazla geniş bir aralık, eklentinin çalışmadığı sürümlerde kurulumun sorunsuz görünmesine yol açar.

Çakışma Ne Zaman Çözülmez

İç içe kurulum her çakışmayı çözmez. Üç durumda tek kopya zorunludur ve karar verilmesi gerekir:

Eş bağımlılık kesişmiyorsa. İki eklenti aynı kütüphanenin uyumsuz ana sürümlerini istiyorsa ikisi bir arada kullanılamaz. Çözüm, eklentilerden birini yükseltmek veya değiştirmektir.

Değerler paketler arasında geçiyorsa. Bir paketin ürettiği nesne başka bir pakete argüman olarak veriliyorsa ve alan taraf kimlik denetimi yapıyorsa, iki kopya çalışmaz.

Çıktı boyutu belirleyiciyse. Tarayıcıya gönderilen bir çıktıda aynı kütüphanenin iki sürümünü taşımak, indirilen baytı iki katına çıkarır.

Bu durumlarda kurulum araçları, geçişli bir bağımlılığın sürümünü dışarıdan sabitlemeye izin veren bir bildirim alanı sunar. Alan işe yarar ama tehlikelidir: bir paketin beklediğinden farklı bir sürümle çalışmaya zorlanması, sürüm sözleşmesinin kasıtlı olarak çiğnenmesidir. Kullanıldığında bir yorumla gerekçelendirilmeli ve geçici sayılmalıdır; kalıcı çözüm, uyumsuz paketin yükseltilmesidir.

Özet

  • Kesişmeyen sürüm istekleri, aynı paketin iki kopyasının ağacın farklı düzeylerine kurulmasıyla karşılanır; arama kuralı her tüketiciye kendi kopyasını bulduran şeydir.
  • Tek değerlendirme güvencesi paket başına değil, çözülmüş dosya yolu başınadır; iki kopya ayrı durum tutar ve nesne kimliği denetimlerini başarısız kılar.
  • Kesişen istekler tek kopyayla karşılanır ve kopya köke çıkarılır; bu düzleştirme, bildirimde yazılmayan paketlerin görünür olmasına da yol açar.
  • Tekillik gerektiren kütüphaneler eş bağımlılık olarak bildirilir; kurma sorumluluğu uygulamaya geçer ve ağaçta tek kopya kalır.
  • Geçişli bir bağımlılığın sürümünü dışarıdan sabitlemek sözleşmeyi çiğner; geçici çözüm sayılmalıdır.

Sonraki Adım

Buraya kadar tüm paketler dışarıdan geldi. Bir ekibin kendi yazdığı paketler de aynı ağaçta yer alır ve aralarındaki bağımlılıklar her değişiklikte yayımlanmayı bekleyemez. Sonraki ders, birden çok paketin tek bir depoda tutulduğu düzeni, bu düzenin çözümlemeyi nasıl değiştirdiğini ve paketler arası derleme sırasının nasıl belirlendiğini ele alır.

İ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