---
title: 'Gem Yapısı ve Yayımlama'
source: 'https://academia.sh/tr/kurslar/ruby-uretim/gem-yapisi-ve-yayimlama'
course: 'Ruby Ekosistemi ve Üretim'
language: tr
updated: '2026-10-21T05:03:44+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Dokuz gözlemin her birinde sonucu kodun, bağımlılığın, sıranın ya da ortamın belirlediğini sınıflandırır'
    - 'Paket tanımından paketin dosya adının ve tam adının türediğini ölçer'
    - 'Çalışma ve geliştirme bağımlılığının paketle birlikte kurulup kurulmadığını ayırır'
    - 'Sürümün dize gibi değil sürüm kuralıyla karşılaştırıldığını gösterir; yayımlanan sürümün içeriğinin değiştirilemediğini ve geri çekmenin sınırını açıklar'
---

# Gem Yapısı ve Yayımlama

Bu kurs bir sonucu neyin belirlediğini sorar. Ders dokuz gözlemlik ölçüm çekirdeğini tanıtır, sonra paket tanımını, dosya adının kaynağını, bağımlılık türlerini ve sürüm karşılaştırmasını ölçer.

Önceki kurs bir değerin ne kadar kaldığını ölçtü ve kapanışında testlerin yalıtımı göründü: iki test aynı uygulamayı paylaşınca kalıcı depo aralarında sızıyordu. Uygulama geliştiricinin makinesinden çıkıp üretime gidince yeni bir soru doğar: kod aynıyken sonuç neden değişiyor? Bu kursun sorusu **bu sonucu ne belirliyor?** sorusudur. Kural, sonucun davranışını okur; kodun nasıl yazıldığını okumaz.

M08/K06'da da bir sonucun sebebi arandı, ama orada sebep kaynaktaki bir çağrı, bir açıklama ya da bir varsayılandı; hepsi kodun içindeydi. Burada kodun **dışındaki** girdi sorulur. Paket tanımı kaynakta yazılıdır ve koddur; kurulan sürüm kaynakta yazılı değildir.

Kural bir sonucu dört sınıftan birine koyar. **Kod**, hesabın yalnız kendi girdisine baktığı durumdur. **Bağımlılık**, kilit dosyasındaki sürümlerin sabitlediği şeydir. **Sıra**, testlerin ya da iş parçacıklarının hangi sırayla koştuğudur. **Ortam**, makinenin söylediğidir: saat, çekirdek sayısı, ortam değişkeni. Bu dört sınıfa **belirleyen** denir. Ölçüm modeli üç girdiden kurulur: `kilit`, `tohum` ve `ortam`. Bu üçünün dışında kalan her şey koddur.

## Ölçüm Çekirdeği

Çekirdek iki dosyadır ve kurs boyunca yeniden yazılmaz, kesiti alınır. `belirleyen.rb` koşum modelini ve sınıflandıran yardımcıyı taşır; blok kaynaktır, çıktısı yoktur.

```ruby
# 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
```

`Kosum` üç girdiyi tutar. `TABAN` bir başlangıç koşumudur; `DEGISKENLER` her seferinde yalnız bir girdiyi değiştirir. `belirleyen(hesap)` hesabı tabanda ve üç değişkende koşturur ve sonucu değiştiren ilk değişkenin adını verir; hiçbiri değiştirmiyorsa sonuç `"kod"`dur. Kanıt sütunu `B± S± O±` biçimindedir: bağımlılık, sıra ve ortam değişkeninde sonucun değişip değişmediği. İkinci dosya, ekosistemin küçük modellerini ve dokuz gözlemi taşır.

```ruby
# gozlemler.rb — dokuz gozlem: bu sonucu ne belirliyor
require "digest"
require_relative "belirleyen"

# Paket tanimi: ad ve surum paketin dosya adini verir.
def paket_tanimi(dosyalar = ["lib/olcer.rb", "lib/olcer/sayac.rb"])
  Gem::Specification.new do |t|
    t.name = "olcer"
    t.version = "0.3.0"
    t.summary = "kucuk bir olcum paketi"
    t.authors = ["ornek yazar"]
    t.files = dosyalar
    t.add_dependency "bicimleyici", "~> 1.4"
  end
end

# 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

# Test kosucusu: testler tohumla karistirilan sirada kosar ve ayni ayarlari paylasir.
TESTLER = {
  "fiyat_bicimi" => ->(ayar) { "12 #{ayar[:para_birimi]}" == "12 TRY" },
  "doviz_cevirimi" => ->(ayar) { ayar[:para_birimi] = "USD"; true },
  "fatura_toplami" => ->(ayar) { ayar[:para_birimi] == "TRY" }
}

def test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar) }
end

# Sahteleme: deneme suresi 30 gundur; saat disaridan verilir.
def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400

# Zamanlayici modeli: iki is parcacigi sayaci okur ve bir fazlasini yazar; okuma ile yazma
# arasinda gecis olabilir. Siradaki adimi hangi is parcacigin atacagini tohum secer.
def kilitsiz_sayac(kosum, artis: 3)
  rastgele = Random.new(kosum.tohum)
  sayac = 0
  isler = Array.new(2) { { kalan: artis, okunan: nil } }
  loop do
    bekleyen = isler.select { |i| i[:kalan].positive? }
    break if bekleyen.empty?

    is = bekleyen.sample(random: rastgele)
    if is[:okunan].nil?
      is[:okunan] = sayac
    else
      sayac = is[:okunan] + 1
      is[:okunan] = nil
      is[:kalan] -= 1
    end
  end
  sayac
end

# Surec tabanli olcekleme: isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir.
def isci_sayisi(ortam) = Integer(ortam[:degiskenler].fetch("ISCI_SAYISI", ortam[:cekirdek]))

# Bellek: blok uc kez kosar; ilk kosumlar isinmadir, son kosumda ayrilan nesneler sayilir.
def ayrilan_nesne
  sonuc = nil
  3.times do
    once = GC.stat(:total_allocated_objects)
    yield
    sonuc = GC.stat(:total_allocated_objects) - once
  end
  sonuc
end

# Profil: programin tanimladigi yontemlerin cagrilari sayilir; en cok cagrilan sicak yoldur.
def sicak_yol
  sayim = Hash.new(0)
  iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
  iz.enable { yield }
  sayim.max_by { |_, n| n }
end

def para_bicimle(kurus) = format("%d.%02d", kurus / 100, kurus % 100)
def satir_bicimle(satir) = "#{satir[:ad]} #{para_bicimle(satir[:birim])} #{para_bicimle(satir[:toplam])}"
def rapor(satirlar) = satirlar.map { |s| satir_bicimle(s) }.join("\n")

# Dagitim: varlik adi icerigin ozetini tasir; yayina kilitteki surumler kurulur.
def varlik_adi(ad, icerik) = ad.sub(/(\.\w+)\z/) { "-#{Digest::SHA256.hexdigest(icerik)[0, 8]}#{Regexp.last_match(1)}" }

def yayin_bildirimi(kosum)
  [varlik_adi("uygulama.css", "body { margin: 0 }"), *kosum.kilit.map { |ad, s| "#{ad}-#{s}" }]
end

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
gozlemler = {
  "paket dosya adi" => ->(_k) { paket_tanimi.file_name },
  "kilitli surum" => ->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4") },
  "test sonucu" => ->(k) { test_kos(k) },
  "saati okuyan test" => ->(k) { deneme_suresinde?(Time.utc(2026, 10, 1), k.ortam[:saat]) },
  "kilitsiz sayac" => ->(k) { kilitsiz_sayac(k) },
  "isci sayisi" => ->(k) { isci_sayisi(k.ortam) },
  "ayrilan nesne" => ->(_k) { ayrilan_nesne { Array.new(1000) { |i| i.to_s } } },
  "sicak yol" => ->(_k) { sicak_yol { rapor(satirlar) } },
  "yayin bildirimi" => ->(k) { yayin_bildirimi(k) }
}

adet = { "kod" => 0, "bagimlilik" => 0, "sira" => 0, "ortam" => 0 }

printf("%-24s%-13s%-10s%s\n", "gozlem", "belirleyen", "kanit", "taban")
gozlemler.each do |ad, hesap|
  tur, kanit, taban = belirleyen(hesap)
  adet[tur] += 1
  printf("%-24s%-13s%-10s%s\n", ad, tur, kanit, taban.inspect)
end

puts
adet.each { |tur, n| printf("%-13s%d\n", tur, n) }
puts "toplam gozlem: #{adet.values.sum}"
```

```
gozlem                  belirleyen   kanit     taban
paket dosya adi         kod          B- S- O-  "olcer-0.3.0.gem"
kilitli surum           bagimlilik   B+ S- O-  "1.4.2"
test sonucu             sira         B- S+ O-  []
saati okuyan test       ortam        B- S- O+  true
kilitsiz sayac          sira         B- S+ O-  6
isci sayisi             ortam        B- S- O+  4
ayrilan nesne           kod          B- S- O-  1001
sicak yol               kod          B- S- O-  [:para_bicimle, 200]
yayin bildirimi         bagimlilik   B+ S- O-  ["uygulama-997facec.css", "bicimleyici-1.4.2", "ayristirici-2.0.1"]

kod          3
bagimlilik   2
sira         2
ortam        2
toplam gozlem: 9
```

Çıktının 3/2/2/2 dağılımı bu kursun haritasıdır. Üç gözlem koddur, ikisi bağımlılık, ikisi sıra, ikisi ortamdır. Her ders bir gözlemi ayrıntılı ölçer; kurs bitince dokuzu da açılmış olur. `kurulan_surum` bir bağımlılık çözücü değil, kilit dosyasındaki sürümü okuyan bir modeldir. Dokuz satırın içinden bu dersin gözlemi ilk satırdır: `paket dosya adi`, kod. **RK1.** Çekirdek gerçek bir paket ekosistemi değil, onun modelidir; bu kursta ölçülen her blok saf Ruby ve standart kütüphanedir.

## Paket Tanımı ve Dosya Adı

Bir **paket**, kodu, adını, sürümünü ve bağımlılıklarını tek birim olarak taşır. Bunları tanımlayan şey **paket tanımı**dır; Ruby'de bu `Gem::Specification` sınıfıdır. Çekirdekteki `paket_tanimi` yardımcısı küçük bir tanım kurar. Bir sonraki dosya aynı tanımı birebir alır; `belirleyen`'e gerek olmadığı için yalnız tanımı çağırır.

```ruby
# paket.rb — paket tanimi: ad ve surum paketin dosya adini verir
# Paket tanimi: ad ve surum paketin dosya adini verir.
def paket_tanimi(dosyalar = ["lib/olcer.rb", "lib/olcer/sayac.rb"])
  Gem::Specification.new do |t|
    t.name = "olcer"
    t.version = "0.3.0"
    t.summary = "kucuk bir olcum paketi"
    t.authors = ["ornek yazar"]
    t.files = dosyalar
    t.add_dependency "bicimleyici", "~> 1.4"
  end
end
```

Şimdi aynı tanımın dosya adına ve tam adına bakılır. Sürümü `0.3.0.pre1` yapmak için `version` alanı değiştirilir; ön sürüm, kararlı sürümden önce gelen sürümdür.

```ruby
# dosya_adi.rb — paketin dosya adi ad ve surumden gelir
require_relative "paket"

t = paket_tanimi
p t.file_name
p t.full_name

t.version = "0.3.0.pre1"
p t.file_name
p t.full_name
```
```
"olcer-0.3.0.gem"
"olcer-0.3.0"
"olcer-0.3.0.pre1.gem"
"olcer-0.3.0.pre1"
```

Dosya adı `ad-surum.gem` biçimindedir. `file_name` uzantıyı taşır, `full_name` taşımaz. Ön sürümde sürüm dizesinin bir parçası olur ve dosya adına girer. Bu, kuralın neden kodu görmesini açıklar: ad ve sürüm kaynakta yazılıdır, kilit dosyasından, tohumdan ya da saatten gelmez. **RK2.** Burada tek bir paket tanımı ölçüldü; çok sayıda alanı olan gerçek bir tanımın aynı kuralı izlediği sınanmadı.

## Çalışma ve Geliştirme Bağımlılığı

Bir paket iki türde bağımlılık taşır. **Çalışma bağımlılığı**, paket kurulurken birlikte gelen şeydir. **Geliştirme bağımlılığı**, yalnız paketi geliştirenin ihtiyaç duyduğu şeydir; testler için kullanılan bir denetim aracı gibi. Paket tanımında ikisi ayrı yöntemlerle yazılır.

```ruby
# bagimliliklar.rb — calisma ve gelistirme bagimliligi
require_relative "paket"

t = paket_tanimi
t.add_development_dependency "denetci"
p t.dependencies.map { |b| [b.name, b.requirement.to_s, b.type] }
p t.runtime_dependencies.map(&:name)
```
```
[["bicimleyici", "~> 1.4", :runtime], ["denetci", ">= 0", :development]]
["bicimleyici"]
```

Birinci satır iki bağımlılığı sürüm kısıtlarıyla ve türleriyle listeler. Geliştirme bağımlılığının kısıtı `>= 0`'dır: hiçbir sürüm sınırı yazılmadı. İkinci satır paketle kurulacak olanları verir ve yalnız `bicimleyici` kalır. Paketi kuran kişi `denetci`'yi almaz. **RK3.** Bağımlılığın sürümünü kilit dosyası sabitler, kısıt yalnız sınırlar; bunun ölçümü sonraki dersin konusudur.

## Sürüm Dizesi Değildir

Bir sürüm numarası noktayla ayrılmış sayılardan oluşur, ama karşılaştırması dize karşılaştırması gibi çalışmaz. Sürüm `0.10.0`, `0.3.0`'dan sonra gelir; dize olarak karşılaştırılırsa ilk farklı karakterde `1` ile `3` karşılaşır ve sıra tersine döner.

```ruby
# surum.rb — surum dize gibi karsilastirilmaz
p "0.10.0" > "0.3.0"
p Gem::Version.new("0.10.0") > Gem::Version.new("0.3.0")

onsurum = Gem::Version.new("1.0.0.pre1")
p onsurum < Gem::Version.new("1.0.0")
p onsurum.prerelease?

begin
  Gem::Version.new("bir.iki")
rescue => e
  p e.class
end
```
```
false
true
true
true
ArgumentError
```

Dize karşılaştırması `false`, sürüm karşılaştırması `true` verir. Ön sürüm, aynı numaralı kararlı sürümden küçüktür ve `prerelease?` ön sürüm olduğunu söyler. Sayıyla başlamayan bir dize (`"bir.iki"`) sürüm sayılmaz ve `ArgumentError` oluşur; `pre1` gibi harfli bir parça ise sayılardan sonra geldiğinde hata vermez, ön sürüm etiketi olarak okunur. **Anlamlı sürümleme**, sürümü ana, alt ve yama olarak üç sayıya böler: ana sürüm uyumsuz değişiklikte, alt sürüm uyumlu yeni özellikte, yama sürüm hata düzeltmesinde artar. Bu bir yazılı sözleşmedir; Ruby onu doğrulamaz. **RK4.** Ölçüm, sürüm kuralının karşılaştırmayı nasıl yaptığını gösterir; bir paketin gerçekten anlamlı sürümleme izlediğini göstermez.

## Yayımlama

Paketi üretmek ve paket deposuna göndermek iki ayrı iştir. İlki paket tanımından `.gem` dosyasını yapar, ikincisi dosyayı herkese açık depoya yükler. Sandbox'ta ne paket komutu ne de ağ vardır, bu yüzden aşağıdaki iki komut koşturulmaz.

```text
gem build olcer.gemspec
gem push olcer-0.3.0.gem
```

`gem build` dosya adını paket tanımından alır; ölçülen `file_name` ile aynı addır. `gem push` ile yayımlanan bir sürümün içeriği sonradan değiştirilemez. Hatalı sürüm `gem yank` ile depo dizininden kaldırılabilir, ama o sürümü indirmiş olanların elindeki kopya yerinde kalır; düzeltilmiş içerik yeni bir sürüm numarasıyla çıkar. Pakete yanlışlıkla bir parola ya da anahtar girdiyse kaldırmak yetmez, o bilgi hemen değiştirilir. Bu yüzden ön sürümler vardır: yayımlamadan önce `0.3.0.pre1` ile denemek, kararlı sürümü korur.

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

Kural yalnız üç girdiyi görür: kilit, tohum, ortam. Çalışma dizini bunlardan biri değildir. Aşağıdaki ölçüm bu sınırı gösterir. Paketin dosya listesi `Dir.glob` ile dizinden okunur; hesap önce dizin boşken, sonra bir dosya yazıldıktan sonra sınıflandırılır.

```ruby
# sinir.rb — dosya listesi calisma dizininden gelince
require_relative "belirleyen"
require_relative "paket"

hesap = ->(_k) { paket_tanimi(Dir.glob("lib/**/*.rb")).files }
p belirleyen(hesap)

Dir.mkdir("lib")
File.write("lib/olcer.rb", "module Olcer; end\n")
p belirleyen(hesap)
```
```
["kod", "B- S- O-", []]
["kod", "B- S- O-", ["lib/olcer.rb"]]
```

Sonuç iki kez `kod` çıktı, ama dosya listesi değişti: önce boş, sonra `lib/olcer.rb`. Kural üç değişkenin hiçbirinde farkı görmedi, çünkü üçü de dizine dokunmaz. Sonuç gerçekte çalışma dizinine bağlıdır. Dizin modelin girdisi olmadığı için kural bu bağımlılığı **kod** diye adlandırır ve bu adlandırma yanıltıcıdır: değişen şey kodun kendisi değil, kodun okuduğu dosya sistemidir. **RK5.** Bu sınır, ölçümün altı çizilmeden bırakılırsa "bu sonuç koda bağlıdır" cümlesini olduğundan güçlü gösterir.

## Özet

- Bu kursun sorusu "bu sonucu ne belirliyor?"dur; kural sonucu kod, bağımlılık, sıra ya da ortam olarak sınıflandırır ve dağılım 3/2/2/2'dir.
- Bu dersin belirleyeni **kod**dur: paketin dosya adı `olcer-0.3.0.gem` ad ve sürümden gelir, `paket dosya adi` satırında `B- S- O-` görünür.
- Çalışma bağımlılığı paketle kurulur, geliştirme bağımlılığı kurulmaz; ön sürüm dosya adına girer.
- Sürüm dize olarak karşılaştırılmaz; `Gem::Version` kuralı `0.10.0 > 0.3.0` der.
- Yayımlanan bir sürümün içeriği değiştirilemez; dosya listesini dizinden okuyan bir paket tanımı kuralın görmediği bir girdiye bağlanır.

## Sonraki Adım

Paketin dosya adını ne belirlediği artık görünüyor. Ama bir paket kurulurken içindeki bağımlılığın hangi sürümünün geleceği paket tanımında yazılı değil. Bir sonraki ders kilit dosyası aynıyken kurulan sürümün neden değişmediğini ölçer.
