İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat