Ders 12 / 16
Çapraz Derleme
Aynı taşınabilir kaynağın üç ayrı hedef için, o hedeflerin hiçbiri kurulu olmadan tek bir komutla derlenip derlenmediği ölçülür; derlenebilirlik burada da koşum gerektirmeyen bir araç bilgisidir. İkinci ölçüm aynı üç hedefte platforma bağlı bir çağrının derlenebilirliğinin değiştiğini gösterir. Sınırlayıcı ölçüm, derlenmiş olmanın hedefte çalışacağı anlamına gelmediğini — aynı adı taşıyan bir varsayımın bir hedef ailesinde geçerli, ötekinde geçersiz kalabileceğini — modelleyerek ortaya koyar.
Yayın Güncelleme
Bu derste
- Aynı taşınabilir kaynağın üç ayrı hedef için, hiçbir hedef makine kurulu olmadan derlenip derlenmediğini araçtan okur
- Platforma bağlı bir çağrının aynı üç hedefte derlenebilirliğinin nasıl değiştiğini ölçerek karşılaştırır
- Derlenmiş olmanın hedefte çalışacağı anlamına gelmediğini, bir varsayımı modelleyerek sınırlar
İçindekiler
Önceki ders, bir dizindeki hangi dosyanın derlemeye gireceğinin, hiçbir program çalıştırmadan, yalnızca araçtan okunabildiğini gösterdi. Bu ders aynı soruyu bir adım öteye taşıyor: dosyalar belli olduktan sonra, o dosyalar hangi hedefe derlenecek? Yanıt yine koşum beklemeden geliyor, ama bu kez sınırı da beraberinde taşıyor — derlenmiş olmak, o hedefte çalışacağı anlamına gelmiyor.
Tek Komut, Üç Hedef
Çapraz derleme (cross compilation), kaynağın derlendiği makineden başka bir hedef için ikili üretme işlemidir. Dilin kendi derleyicisi bu işi tek bir komutla yapıyor; hedefi seçen şey iki ortam değişkeni — biri hedef işletim sistemi ailesini, öteki işlemci mimarisini belirliyor. Bu iki değişken derleme komutunun dışından geliyor, tıpkı önceki dersteki derleme etiketleri gibi; farkları, bir etiketin adı kaynağa özgüyken bu iki değişkenin adının ve alabileceği değerlerin dilin kendisi tarafından sabitlenmiş olması. Üçüncü bir hedef makineye ihtiyaç yok: derleyici, çalıştığı makineden bağımsız olarak, istenen hedef için bir ikili üretiyor ve bunu üretebilip üretemediği yine araç sınıfına giren bir bilgi — hiçbir şey çalıştırılmadan öğreniliyor.
Bu, önceki dersle aynı temel düzenekten besleniyor. Standart kitaplığın kendisi, birçok işlevi her hedef ailesi için ayrı bir dosyada, o ailenin adını taşıyan bir derleme etiketinin (ya da eşdeğer bir dosya adı sözleşmesinin) arkasında tutuyor; derleyici, hangi hedef için çalıştığını iki ortam değişkeninden okuyup doğru dosya kümesini seçiyor. Aradaki fark ölçek: önceki derste altı dosyalık bir örnekte elle kurulan seçim, standart kitaplığın kendisinde binlerce dosya üzerinde, her hedef için ayrı ayrı çalışıyor. Bu yüzden bir hedefin “kurulu” olması gerekmiyor — gereken tek şey, derleyicinin o hedef için hangi kaynak dosyalarının geçerli olduğunu bilmesi ve bu bilgi de kaynağın kendisinde, dosya adlarında ve etiketlerinde zaten duruyor.
Aşağıdaki ölçüm taşınabilir, hiçbir platforma özgü çağrı içermeyen bir kaynağı üç ayrı hedef için derliyor ve her denemenin geçip geçmediğini okuyor. Üretilen ikili hiçbir yere yazılmıyor — çıktı sistemin boş aygıtına yönlendiriliyor, çünkü bu derste ölçülen tek şey derlemenin başarısı, üretilen dosyanın kendisi değil.
// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
)
// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
fmt.Printf("%-42s %-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("", "capraz")
if err != nil {
return ""
}
os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module capraz\n\ngo 1.24\n"), 0o644)
for ad, icerik := range dosyalar {
os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
}
return dizin
}
// derlenebilirMi, kaynagi verilen hedef icin derlemeyi dener; cikti hicbir zaman basilmaz,
// yalniz basarili olup olmadigi donuyor. Uretilen ikili sistemin bos aygitina yaziliyor.
func derlenebilirMi(dizin string, ekOrtam []string) bool {
cmd := exec.Command("go", "build", "-o", os.DevNull, ".")
cmd.Dir = dizin
cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local", "CGO_ENABLED=0"), ekOrtam...)
return cmd.Run() == nil
}
// ucHedef.go — tasinabilir bir kaynak uc ayri hedef icin, hicbir hedef makine gerekmeden derleniyor
package main
import "fmt"
func main() {
dizin := paket(map[string]string{
"tasinabilir.go": "package main\n\nfunc topla(a, b int) int { return a + b }\n\nfunc main() {\n\t_ = topla(1, 2)\n}\n",
})
defer func() { _ = dizin }()
hedefler := []struct{ ad, goos, goarch string }{
{"birinci hedef", "linux", "amd64"},
{"ikinci hedef", "windows", "amd64"},
{"ucuncu hedef", "darwin", "arm64"},
}
fmt.Println("-- tek komut, uc hedef: tasinabilir kaynak --")
for _, h := range hedefler {
ortam := []string{"GOOS=" + h.goos, "GOARCH=" + h.goarch}
goster(h.ad+" derlendi mi", "arac", fmt.Sprint(derlenebilirMi(dizin, ortam)))
}
}
-- tek komut, uc hedef: tasinabilir kaynak -- birinci hedef derlendi mi arac true ikinci hedef derlendi mi arac true ucuncu hedef derlendi mi arac true
DR6. Üç ayrı hedefin üçü de derlendi ve bu makinede üç hedefin de gerçek bir kopyası hiç
yoktu — çalıştırılan tek şey derleyicinin kendisi, iki ortam değişkeniyle üç kez çağrıldı.
Sonuç yine koşum sütununu hiç doldurmuyor: taşınabilir bir kaynağın hangi hedefler için
derlendiği, o hedeflerin hiçbirine dokunmadan, tek bir komutun geçti/kaldı yanıtından
okunuyor.
Aynı Çağrı, Değişen Sonuç
Yukarıdaki kaynak platforma özgü hiçbir şey içermiyordu, bu yüzden üç hedefin üçü de aynı yanıtı verdi. Bir çağrı platforma özgü bir sistem işlevine bağlandığında bu tekdüzelik bozuluyor — bazı işletim sistemi aileleri o işlevi tanımlarken, bazıları hiç tanımlamıyor. Aşağıdaki ölçüm aynı üç hedefte, standart kitaplığın belirli bir işletim sistemi ailesine özgü tek bir sistem çağrısını (adlandırılmış boru oluşturma çağrısı) kullanan bir kaynağı deniyor.
// sistemCagrisi.go — ayni cagri, uc hedef: platforma bagli bir sistem cagrisi
package main
import "fmt"
func main() {
dizin := paket(map[string]string{
"cagrili.go": "package main\n\nimport \"syscall\"\n\nfunc main() {\n\t_ = syscall.Mkfifo\n}\n",
})
defer func() { _ = dizin }()
hedefler := []struct{ ad, goos, goarch string }{
{"birinci hedef", "linux", "amd64"},
{"ikinci hedef", "windows", "amd64"},
{"ucuncu hedef", "darwin", "arm64"},
}
fmt.Println("-- ayni cagri, uc hedef: platforma bagli bir sistem cagrisi --")
basarili := 0
for _, h := range hedefler {
ortam := []string{"GOOS=" + h.goos, "GOARCH=" + h.goarch}
d := derlenebilirMi(dizin, ortam)
if d {
basarili++
}
goster(h.ad+" derlendi mi", "arac", fmt.Sprint(d))
}
goster("uc hedeften kaci derlendi", "arac", fmt.Sprint(basarili))
}
-- ayni cagri, uc hedef: platforma bagli bir sistem cagrisi -- birinci hedef derlendi mi arac true ikinci hedef derlendi mi arac false ucuncu hedef derlendi mi arac true uc hedeften kaci derlendi arac 2
DR7. Aynı kaynak, aynı üç hedef, ama bu kez sonuç tekdüze değil: iki hedef derliyor, biri derlemiyor. Standart kitaplığın sistem çağrısı paketi her işletim sistemi ailesi için ayrı bir gerçekleştirim taşıyor ve bir ailede hiç bulunmayan bir işlevi çağırmak, o aile hedeflenince derlemeyi durduruyor — üstelik bu, önceki derste ölçülen “hangi dosya derlemeye giriyor” sorusuyla aynı katmanda değil, bir düzey daha derinde: dosyanın kendisi her üç hedefte de derleme listesine giriyor, ama dosyanın içindeki bir ad üç hedefin ikisinde tanımlı, birinde tanımsız kalıyor. Hangi ailenin hangi çağrıyı tanımladığı da yine araç bilgisi: bir kişi bunu ezbere bilebilir, ama derlemenin kendisi bunu her seferinde aynı şekilde, koşum gerektirmeden doğruluyor.
Bu farkın pratikteki sonucu, birden çok hedefi aynı anda desteklemesi gereken bir kaynağın
iki yoldan biriyle yazılması gerektiği. Ya platforma özgü çağrı hiç kullanılmıyor — önceki
bölümdeki taşınabilir kaynak gibi, üç hedefin üçünde de aynı davranışı veriyor — ya da
platforma özgü kısım kendi dosyasına, kendi etiketine ayrılıyor ve her hedef için ayrı bir
gerçekleştirim yazılıyor. İkinci yol, önceki dersteki genisleme/deney örneğiyle aynı
biçimi taşıyor: dosya adı ya da etiket, hangi hedefte hangi gerçekleştirimin devreye
gireceğini belirliyor. Fark, seçimin bu kez elle verilen bir bayrakla değil, derleme
komutunun kendi hedef bilgisiyle otomatik yapılması. Tek bir dosyanın üç hedefin ikisinde
derlenip birinde derlenmemesi bu yüzden bir kusur değil — kaynağın, o çağrıyı desteklemeyen
hedefler için hiç bir alternatif dosya sağlamadığının bir işareti.
Sınırlayıcı Ölçüm: Derlenmek Çalışmak Değildir
Yukarıdaki iki ölçüm de aynı sınırın içinde kaldı: bir kaynağın belirli bir hedef için derlenip derlenmediği. Bu, o kaynağın o hedefte çalışıp çalışmayacağından ayrı bir soru ve bu ders o ikinci soruyu gerçekten yanıtlayamıyor — üç hedeften ikisinin gerçek bir kopyası bu makinede yok, üretilen ikili çalıştırılamıyor. Bu sınırı gizlemek yerine açıkça modelliyoruz: aşağıdaki ölçüm gerçek bir hedefte hiç çalıştırılmıyor, iki hedef ailesinin ortamı kaynak içinde elle kuruluyor.
// varsayimModeli.go — sinirlayici olcum: derlenmek calismak degildir, model uzerinden gosteriliyor
package main
import "fmt"
// evYoluBul, verilen ortam haritasinda TEK bir degisken adina bakarak ev dizinini arar.
func evYoluBul(ortam map[string]string, degiskenAdi string) (string, bool) {
deger, varMi := ortam[degiskenAdi]
return deger, varMi
}
func main() {
// iki hedef ailesinin ortami burada gercekten calistirilmadan, elle modelleniyor:
// her aile kullanicinin ev dizinini kendi adiyla tasiyan bir degisken sunuyor.
birinciAileOrtami := map[string]string{"HOME": "kullanici-dizini"}
ikinciAileOrtami := map[string]string{"USERPROFILE": "kullanici-dizini"}
// tasinabilir goruntusune ragmen, kod yalnizca TEK bir degisken adini ariyor.
arananDegisken := "HOME"
_, birinciBuldu := evYoluBul(birinciAileOrtami, arananDegisken)
_, ikinciBuldu := evYoluBul(ikinciAileOrtami, arananDegisken)
fmt.Println("-- sinirlayici olcum: derlenmek calismak degildir (model) --")
goster("birinci hedef ailesinde ev yolu bulundu mu", "arac", fmt.Sprint(birinciBuldu))
goster("ikinci hedef ailesinde ev yolu bulundu mu", "arac", fmt.Sprint(ikinciBuldu))
}
-- sinirlayici olcum: derlenmek calismak degildir (model) -- birinci hedef ailesinde ev yolu bulundu mu arac true ikinci hedef ailesinde ev yolu bulundu mu arac false
DR8. evYoluBul işlevi platforma özgü hiçbir çağrı içermiyor — tek bir dizgi anahtarıyla
bir haritaya bakıyor — bu yüzden yukarıdaki gibi üç gerçek hedef için derlenseydi üçünde de
sorunsuz derlenirdi. Kusur derlemede değil, varsayımda: kod yalnızca tek bir değişken
adını arıyor ve bu ad birinci hedef ailesinde geçerli, ikincisinde geçersiz. Gerçek bir
programda bu satır, bir kullanıcının ayar dizinini bulmaya çalışan bir çağrı olabilir; ikinci
hedef ailesinde çalıştırıldığında sessizce boş dönebilir, hiçbir derleme hatası, hiçbir
çalışma zamanı çökmesi olmadan. Bu yüzden “derlendi=true” satırı, önceki iki ölçümde olduğu
gibi burada da güvenilir bir bilgi — ama neyin doğru olduğunu söylüyor, neyin iyi çalıştığını
değil. Bu ayrım modellenmeden ölçülemiyor, çünkü koşumun kendisi bu derste hiçbir zaman gerçek
bir hedefte gerçekleşmiyor.
Modelin kendisi kasıtlı olarak küçük tutuldu: iki harita, bir dizgi anahtarı, iki arama. Gerçek bir programda bu tür bir varsayım genellikle çok daha derine gömülü olur — bir yapılandırma dosyasının aranacağı dizin, bir günlük dosyasının yazılacağı yer, bir geçici dosyanın adı — ve hepsi aynı örüntüyü paylaşır: kod bir tek adı, bir tek yolu ya da bir tek biçimi doğru kabul eder, bu kabul üç hedefin de derlemesini geçer ve yalnızca yanlış olduğu hedefte, o hedef gerçekten çalıştırıldığında ortaya çıkar. Derlenebilirlik ölçümünün gücü tam burada bitiyor: söylediği şey doğru ve güvenilir, ama sorduğu soru dar — yalnızca “bu kaynak bu hedef için bir ikiliye dönüşür mü” sorusuna yanıt veriyor, “bu ikili doğru şeyi yapar mı” sorusuna değil.
Bu sınırın pratikte kapatılma yolu, yine aynı üç sınıfın içinde kalıyor: bir hedefte gerçekten çalıştığını göstermenin tek güvenilir yolu, o hedefte gerçekten koşturmak — gerçek bir makinede ya da o hedefi taklit eden bir ortamda. Derlenebilirlik sınaması bu adımın yerini almıyor, önüne geçiyor: bir kaynak üç hedefin birinde bile derlenmiyorsa, o hedefte hiç koşum denemeye gerek kalmıyor. Üç hedefin üçünde de derlenen bir kaynak için ise soru hâlâ açık — derlenebilirlik testi bu açık soruyu erken, ucuz bir adımda eliyor, ama kapatmıyor. Bu derste ikinci adım (gerçek koşum) hiç yapılmadı, çünkü üç hedeften ikisinin bu makinede gerçek bir kopyası yok; bu, ölçümün kapsamının bir sınırı, çapraz derlemenin kendisinin bir sınırı değil.
Aynı sınır, çapraz derlemenin kendi sınırlarından biri için de geçerli: kaynak cgo üzerinden
bir yerel kütüphaneye bağlanıyorsa, derleyici genellikle hedef için gerekli dış derleyiciyi
bulamıyor ve derleme bu kez gerçekten reddediliyor — bu, kursun son dersinin konusu. Bu derste
ölçülen üç hedefin üçü de saf kaynaktı; bir dış derleyici hiç gerekmedi ve bu yüzden hiçbirinde
reddedilme yaşanmadı.
Özet
- Çapraz derleme, hedef bir sistem çevresi hiç kurulu olmadan, tek bir komutla o hedef için ikili üretme işlemidir; derlenebilirlik burada da koşum gerektirmeyen bir araç bilgisi.
- Taşınabilir bir kaynak üç hedefin üçünde de aynı yanıtı verdi; platforma bağlı tek bir sistem çağrısı eklendiğinde aynı üç hedeften yalnızca ikisi derlendi.
- Bir çağrının hangi hedef ailesinde tanımlı olduğu da araçtan okunan bir bilgi — derleme, bu bilgiyi her denemede aynı şekilde, hiçbir kişisel bilgiye ihtiyaç duymadan doğruluyor.
- Sınırlayıcı ölçüm: derlenmiş olmak hedefte doğru çalışacağı anlamına gelmiyor; aynı ada bağlı bir varsayım bir hedef ailesinde geçerli, ötekinde geçersiz kalabiliyor.
- Bu sınır gerçek bir hedefte koşturularak değil, iki hedefin ortamı kaynak içinde açıkça modellenerek gösterildi; kursun hiçbir yerinde gerçek bir hedef makineye bağlanılmadı.
Sonraki Adım
Bu derste hedef, dosyanın kendisinin hiç değişmediği bir seçimdi — kaynak sabitti, yalnızca hangi ortama derlendiği değişti. Sıradaki ders kaynağın içine hiç yazılmayan, ama yine de ikiliye giren bir değeri ele alıyor: bir sürüm bilgisi, kaynağın hiçbir satırında durmadan, yalnızca derleme anında bir başka araç tarafından ikiliye yerleştiriliyor. Bu kez sorulan soru “hangi hedef” değil, “kaynakta hiç yazılmayan bir değer nereden geliyor” — ve bu derste seçilen hedefin ikiliye hiçbir iz bırakmadığı gibi, o değer de kaynağın kendisinde hiçbir iz bırakmıyor; ikisi de yalnızca derleme sürecinin kendi araçlarıyla, kaynağın dışından ikiliye ekleniyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.