İçeriğe geç
academia.sh

Ders 11 / 16

Derleme Etiketleri

Aynı dizindeki altı kaynak dosyadan hangisinin derlemeye gireceği, çalıştırılan bir programa değil araca sorularak öğrenilir; dört etiket senaryosunda dosya sayısı bu şekilde okunur. Sınırlayıcı ölçüm, etiketiyle dışarıda kalan bir dosyanın diskten silinmediğini ama hiçbir aracın onu incelemediğini — dosyadaki bir kusurun yalnızca etiket etkinleşince görünür hale geldiğini — koşturarak gösteriyor.

Yayın Güncelleme

Bu derste

  • Bir dizindeki kaynak dosyaların, hiçbir program çalıştırmadan, yalnızca araçtan sorularak kaç tanesinin derlemeye girdiğini ölçer
  • Derleme etiketlerindeki VE, VEYA ve DEĞİL mantığının dosya sayısını nasıl değiştirdiğini dört senaryoda karşılaştırır
  • Etiketiyle dışarıda kalan bir dosyanın diskte durmaya devam ettiğini ama hiçbir aracın onu sınamadığını gösterir
İçindekiler

Başarım ve Bellek konusunun son dersi bir izin, topladığı anda programın davranışını değiştirdiğini ölçtü; sayılar bu yüzden hep bu etkinin altında okundu. Bu ders aynı üç sınıftan (kaynak, araç, koşum) yalnızca birine bakıyor ve koşumu tamamen devre dışı bırakıyor: bir program hiç çalıştırılmadan önce, hatta hiç derlenmeden önce bile, kaynak ağacındaki hangi dosyanın o derlemeye gireceği zaten belli. Bu kararı veren şey ne bir kişinin okuması ne de çalışan bir programın kendisi — yalnızca araç, kaynağın üstündeki bir yönergeyi okuyup dosya listesini süzüyor. Sıradaki dört ders de aynı örüntüyü izliyor: hangi platforma çıkılacağı, ikiliye ne gömüleceği, hangi sürümün uyumlu sayılacağı ve hangi kalıbın kusurlu bulunacağı — hepsi koşum beklemeden, yalnızca araçtan okunuyor.

Derleme Etiketi Nedir

Derleme etiketi (build tag), bir kaynak dosyanın en üstüne, paket bildiriminden önce yazılan özel bir yorum satırıdır: //go:build. Yönergenin çalışabilmesi için üç biçim kuralı var. Yorum dosyanın en üst satırlarından birinde durmalı; paket bildiriminden önce en az bir boş satır bırakılmalı; ve yönerge ile dosyanın geri kalanı arasında hiçbir kod satırı girmemeli. Bu üç kural ihlal edilirse derleyici yönergeyi sıradan bir yorum sanır ve dosya her zaman derlemeye girer — yönergenin kendisi bir hataya değil, sessizce yok sayılmaya yol açar.

Yönergenin gövdesi küçük bir mantık ifadesidir: bir ad tek başına yazılabilir (genisleme), iki ad && ile birleştirilip ikisinin de aynı anda etkin olması istenebilir, || ile ikisinden en az birinin etkin olması istenebilir, ya da başına konan bir değil işaretiyle bir adın etkin olmaması istenebilir. Bu üç işleç herhangi bir derinlikte parantezle birleştirilebiliyor, ama bu dersin ölçtüğü altı dosya üçünü de yalnızca bir kez kullanıyor — mantığın kendisini karmaşıklaştırmadan üç köşesini de görmek için bu yeterli.

Bu mantığın pratikteki karşılığı genellikle iki türden bir ayrım. Birincisi, bir paketin normalde derlenmeyen, yalnızca bir sınama ya da bir tanılama oturumunda etkinleştirilen bir ek dosya taşıması — kaynak ağacında durur, ama gündelik derlemeyi büyütmez ya da yavaşlatmaz. İkincisi, aynı paketin birbirini dışlayan iki gerçekleştirimi taşıması — biri varsayılan, öteki yalnızca belirli bir etiket verildiğinde devreye giren bir değişken. İkisinde de ortak olan şey, seçimin derleme anında, kaynağın kendisine hiç dokunmadan yapılabilmesi; aynı depo, aynı dal, aynı commit, farklı -tags bayrağıyla farklı bir programa dönüşebiliyor.

Etiketin adları program dışından geliyor: kaynağın kendisinde hiçbir yerde tanımlanmıyorlar, yalnızca derleme komutuna -tags bayrağıyla veriliyor. Bu, önceki konudaki derleme sabitlerinden temel bir farkı işaret ediyor — kaynak içinde const ile yazılan bir değer her derlemede aynı kalırken, bir derleme etiketi kaynağın dışından, her çağrıda ayrı ayrı belirleniyor. Aynı kaynak ağacı, hiçbir satırı değişmeden, verilen etiket kümesine göre tamamen farklı bir dosya kümesinden derlenebiliyor.

Dört Senaryo, Bir Mantık

Aşağıdaki ölçüm altı dosyalık küçük bir paket kuruyor — biri etiketsiz, beşi farklı bir etiket ifadesiyle işaretli — ve dört ayrı etiket kümesiyle derlemeye giren dosya sayısını okuyor. Paket gerçek bir dizine değil, os.MkdirTemp ile açılan geçici bir modüle yazılıyor; bu dersin geri kalanında da yol hiçbir yerde basılmıyor.

// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-38s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "etiket")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module etiketdeneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, cikisin basarili olup olmadigini doner; cikti basilmaz.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// dosyaSayisi, dizindeki .go uzantili dosyalarin toplam sayisini hicbir arac cagirmadan sayar.
func dosyaSayisi(dizin string) int {
	girdiler, _ := os.ReadDir(dizin)
	n := 0
	for _, g := range girdiler {
		if strings.HasSuffix(g.Name(), ".go") {
			n++
		}
	}
	return n
}

// goFilesSayisi, verilen etiket kumesiyle derlemeye giren dosya sayisini araçtan okur.
func goFilesSayisi(dizin, etiketler string) int {
	arg := []string{"list", "-f", "{{len .GoFiles}}", "."}
	if etiketler != "" {
		arg = []string{"list", "-tags", etiketler, "-f", "{{len .GoFiles}}", "."}
	}
	cikti, _ := aracCalistir(dizin, arg...)
	n, _ := strconv.Atoi(strings.TrimSpace(cikti))
	return n
}

// alanSayisi, "go list" ciktisindaki herhangi bir listeleme alaninin (GoFiles, TestGoFiles)
// uzunlugunu okur; hangi alanin sorulacagi sablon parametresiyle verilir.
func alanSayisi(dizin, sablon string) int {
	cikti, _ := aracCalistir(dizin, "list", "-f", sablon, ".")
	n, _ := strconv.Atoi(strings.TrimSpace(cikti))
	return n
}
// olcum.go — hangi dosyalar derlemeye giriyor, dort etiket senaryosunda arac uzerinden okunuyor
package main

import "fmt"

func main() {
	dosyalar := map[string]string{
		"cekirdek.go": "package main\n\nfunc temelIslev() int { return 1 }\n",
		"genisleme.go": "//go:build genisleme\n\npackage main\n\nfunc genislemeIslevi() int { return 2 }\n",
		"deney.go": "//go:build deney\n\npackage main\n\nimport \"fmt\"\n\nfunc deneyIslevi() int {\n\tfmt.Printf(\"%d\\n\", \"hata\")\n\treturn 3\n}\n",
		"ikisi_birden.go": "//go:build genisleme && deney\n\npackage main\n\nfunc ikisiBirdenIslevi() int { return 4 }\n",
		"herhangi.go": "//go:build genisleme || deney\n\npackage main\n\nfunc herhangiIslevi() int { return 5 }\n",
		"hic_degil.go": "//go:build !deney\n\npackage main\n\nfunc hicDegilIslevi() int { return 6 }\n",
	}
	dizin := paket(dosyalar)
	defer func() { _ = dizin }()

	fmt.Println("-- hangi dosyalar derlemeye giriyor: dort etiket senaryosu --")
	goster("etiketsiz dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "")))
	goster("genisleme etiketiyle dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "genisleme")))
	goster("deney etiketiyle dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "deney")))
	goster("iki etiketle birden dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "genisleme,deney")))

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: disarida kalan dosya silinmiyor, sinanmiyor --")
	goster("diskteki .go dosyasi sayisi", "arac", fmt.Sprint(dosyaSayisi(dizin)))
	goster("etiketsiz derlemeye giren sayi", "arac", fmt.Sprint(goFilesSayisi(dizin, "")))

	_, etiketsizTemiz := aracCalistir(dizin, "vet", "./...")
	_, deneyliTemiz := aracCalistir(dizin, "vet", "-tags", "deney", "./...")
	goster("etiketsiz cozumleme sonucu", "arac", fmt.Sprint("temiz=", etiketsizTemiz))
	goster("deney etiketiyle cozumleme sonucu", "arac", fmt.Sprint("temiz=", deneyliTemiz))
}
-- hangi dosyalar derlemeye giriyor: dort etiket senaryosu --
etiketsiz dosya sayisi                 arac    2
genisleme etiketiyle dosya sayisi      arac    4
deney etiketiyle dosya sayisi          arac    3
iki etiketle birden dosya sayisi       arac    5

-- sinirlayici olcum: disarida kalan dosya silinmiyor, sinanmiyor --
diskteki .go dosyasi sayisi            arac    6
etiketsiz derlemeye giren sayi         arac    2
etiketsiz cozumleme sonucu             arac    temiz=true
deney etiketiyle cozumleme sonucu      arac    temiz=false

DR1. Bu on satırın hiçbiri bir program çalıştırmıyor; deneyIslevi ve ikisiBirdenIslevi gibi işlevlerin gövdesi hiç yürütülmedi. Ölçülen tek şey, dosya listesinin kendisi — bu yüzden tablonun tamamı araç sınıfına yazılıyor, kursun koşum sütunu bu derste hiç dolmuyor.

Altı dosyanın etiketleri üç işlecin üçünü de örnekliyor: cekirdek.go hiç etiketsiz, genisleme.go ve deney.go birer tek ad, ikisi_birden.go bir &&, herhangi.go bir ||, hic_degil.go da başına değil işareti konmuş bir deney. Etiketsiz çağrıda yalnızca etiketsiz dosyayla değil-işaretli koşulu sağlayan iki dosya kalıyor — deney etkin olmadığı için hic_degil.go da içeride. genisleme etiketiyle çağrıldığında hem kendi etiketli dosyası hem || koşulunu sağlayan herhangi.go tabloya ekleniyor, ama && koşulunu sağlamak için ikinci etiket eksik olduğundan ikisi_birden.go hâlâ dışarıda. DR2. Yalnız her iki etiket birden verildiğinde ikisi_birden.go tabloya giriyor ve aynı anda hic_degil.go çıkıyor — çünkü artık deney etkin ve değil-işaretli koşul bozuluyor. Dört sayı (2, 4, 3, 5) bu yüzden dört ayrı mantık değerlendirmesinin sonucu, dört ayrı çalıştırmanın değil.

Adlandırma da Bir Örtük Etiket: _test.go

Derleme etiketi tek mekanizma değil. Bir dosya adının sonuna eklenen _test.go soneki, hiçbir //go:build yönergesi yazılmadan aynı soruya yanıt veriyor: bu dosya normal derlemeye girer mi? Yanıt hayır — ama bu, dosyanın hiç derlenmediği anlamına gelmiyor; yalnızca ayrı bir listede, TestGoFiles altında derleniyor. Aşağıdaki ölçüm iki dosyalık bir paket kuruyor — biri etiketsiz, biri _test.go soneki taşıyan bir sınama dosyası — ve iki ayrı alanı okuyor.

// adlandirma.go — _test.go soneki, hicbir yonerge yazilmadan dosyayi normal derlemeden cikariyor
package main

import "fmt"

func main() {
	dosyalar := map[string]string{
		"cekirdek.go": "package main\n\nfunc temelIslev() int { return 1 }\n",
		"cekirdek_test.go": "package main\n\nimport \"testing\"\n\nfunc TestTemelIslev(t *testing.T) {\n\tif temelIslev() != 1 {\n\t\tt.Fatal(\"beklenmeyen\")\n\t}\n}\n",
	}
	dizin := paket(dosyalar)
	defer func() { _ = dizin }()

	fmt.Println("-- adlandirma da bir ortuk etiket: _test.go --")
	goster("diskteki .go dosyasi sayisi", "arac", fmt.Sprint(dosyaSayisi(dizin)))
	goster("normal derlemeye giren dosya sayisi", "arac", fmt.Sprint(alanSayisi(dizin, "{{len .GoFiles}}")))
	goster("sinama icin derlenen dosya sayisi", "arac", fmt.Sprint(alanSayisi(dizin, "{{len .TestGoFiles}}")))
}
-- adlandirma da bir ortuk etiket: _test.go --
diskteki .go dosyasi sayisi            arac    2
normal derlemeye giren dosya sayisi    arac    1
sinama icin derlenen dosya sayisi      arac    1

DR3. İki dosya diskte duruyor ama hiçbiri diğerinin listesine karışmıyor: normal derleme yalnızca cekirdek.goyu görüyor, sınama derlemesi yalnızca cekirdek_test.goyu. Bu ayrım bir yönergeye değil, saf bir ada dayanıyor — dosya adının sonunda _test.go görmek, aracın kendisine gömülü, kaynakta hiç yazılmayan bir kuraldır. //go:build yönergesi bu kuralın üstüne, isteğe bağlı bir ikinci katman ekliyor; adlandırma sözleşmesi hep orada duruyor ve her paket için otomatik işliyor. İki mekanizma da aynı soruyu yanıtlıyor — hangi dosya hangi listeye giriyor — ama biri kaynakta açıkça yazılı bir yönergeye, öteki yalnızca bir ada dayanıyor.

Sınırlayıcı Ölçüm: Dosya Silinmiyor, Sınanmıyor

Yukarıdaki tablonun ikinci yarısı bambaşka bir soru soruyor: etiketiyle dışarıda kalan bir dosyaya ne oluyor? Yanıt iki parçalı ve ikisi de ölçülüyor. Birincisi: dizindeki .go dosyası sayısı her zaman altı — hiçbir etiket kombinasyonu bir dosyayı diskten silmiyor, yalnızca o çalıştırmanın derleme listesinden çıkarıyor. dosyaSayisi işlevi bunu doğrudan os.ReadDir ile sayıyor, hiçbir go komutu çağırmadan; DR4. diskteki altı dosyaya karşılık etiketsiz derlemeye giren yalnızca iki — aradaki dört dosya ne kaybolmuş ne bozulmuş, yalnızca o çalıştırmanın görüş alanının dışında duruyor.

İkincisi ve daha keskin olanı: deney.go içine bilinçli olarak bir kusur yazıldı — %d biçimlendiricisine bir dizgi veriliyor, bu tür bir uyuşmazlığı derleyici değil, Go Komut Ailesi dersinde tanıtılan inceleyici yakalar. Etiketsiz çalıştırıldığında bu araç temiz=true diyor, çünkü kusurlu dosya derleme listesinde hiç yok — araç onu okumuyor bile. DR5. Aynı araç deney etiketiyle çağrıldığında temiz=falsea dönüyor; kusur değişmedi, yalnızca dosyanın görünürlüğü değişti. Bu, “etiketle dışarıda kalan dosya sınanmaz” iddiasının tam kanıtı: kusur, etiketi etkinleştiren biri onu çalıştırana kadar hiçbir aracın erişemeyeceği bir köşede bekliyor.

Bu sınır pratikte iki yönlü çalışıyor. Bir yandan, deneysel ya da platforma özgü kod bir etiketin arkasına yazıldığında, o kodun kalitesi ana derlemenin ve ana çözümlemenin hiçbir zaman denetlemediği bir alanda kalıyor — etiket yalnızca hangi ortamda derleneceğini belirliyor, o kodun doğruluğunu garanti etmiyor. Öte yandan, bu tam olarak etiketlerin var oluş nedeni: bir kaynak ağacı, aynı anda çalışması beklenmeyen birden çok varyantı taşıyabiliyor — ana akışın dışında kalan bir varyant, ana akışı hiç etkilemeden diskte durabiliyor. Etiketin kararı derleme öncesinde, tek bir metin karşılaştırmasıyla veriliyor; bu yüzden hem çok ucuz hem de çok kesin, ama sonuçları yalnızca etiketi veren tarafından görülebiliyor.

Bu iki yön arasındaki denge bir sorumluluk paylaşımına dönüşüyor. Bir dosyayı bir etiketin arkasına yazan kişi, o dosyanın normal akışın gözünden kaybolduğunu biliyor; dolayısıyla onu düzenli aralıklarla etiketiyle birlikte derlemek ve çözümlemek, o dosyanın kalitesini sürdürmenin tek yolu haline geliyor. Etiket kendi başına hiçbir hatırlatma yapmıyor — deney.go içindeki kusur, biri açıkça -tags deney vermediği sürece haftalarca, hatta kaynak ağacı büyüdükçe fark edilmeden kalabilir. Bu ders bu riski somut bir sayı üzerinden gösteriyor: altı dosyalık küçük bir örnekte fark tek bir satırdı, ama gerçek bir depoda etiketli dosya sayısı arttıkça, etiketsiz derlemenin ve etiketsiz çözümlemenin görmediği kaynak miktarı da büyüyor — üstelik bu miktarın kendisi, yine yalnızca aracın kendisine sorularak, dosya listesi karşılaştırılarak ölçülebiliyor.

Özet

  • Derleme etiketi, bir dosyanın derlemeye girip girmeyeceğini kaynağın dışından, -tags bayrağıyla belirleyen bir yönergedir; kararı hiçbir program çalıştırmadan araç verir.
  • Etiket ifadeleri &&, || ve değil işareti ile kurulur; aynı altı dosyalık kaynak, verilen etiket kümesine göre iki ile beş arasında değişen bir dosya listesi üretebiliyor.
  • Etiketiyle dışarıda kalan bir dosya diskten silinmiyor — yalnızca o çalıştırmanın derleme listesinin dışında kalıyor; dizindeki dosya sayısı her zaman sabit kalıyor.
  • Dışarıda kalan bir dosyadaki kusur, o dosyayı hiçbir araç okumadığı için görünmüyor; aynı araç, etiket etkinleştirildiğinde aynı kusuru hemen buluyor.
  • Bu ders koşum sınıfını hiç kullanmadı — bütün sonuçlar, hiçbir işlev çağrılmadan, yalnızca dosya listesi ve çözümleme sonucu okunarak elde edildi.

Sonraki Adım

Bu derste ölçülen soru “hangi dosya” ile sınırlıydı — dosyalar belli olduktan sonra hepsi aynı tek bir hedefe, çalıştırılan makinenin kendisine derleniyordu. Sıradaki ders aynı sorunun bir adım ötesini soruyor: aynı kaynak, hiç değişmeden, makineden tamamen farklı bir hedefe derlenebilir mi? Yanıt yine koşum beklemeden, tek bir komutun verdiği geçti/kaldı bilgisinden okunuyor — ama bu kez derlenmiş olmanın, hedefte çalışacağı anlamına gelmediği bir sınırla birlikte.

İ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