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,
-tagsbayrağı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şumsı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.