İçeriğe geç
academia.sh

Ders 01 / 12

Derleme Aracı Modeli

Bir bildirimden bir derleme aracının türettiği evre dizisi sabittir ve bildirimin yazılış sırasından etkilenmez: üç bildirilen ögeden dört evre çıkar, ileri sıralı ve geri sıralı aynı bildirimden aynı dört evre türer. Aynı bildirimden çıkan iki sınıf yolu — biri derlemenin, biri koşumun — kaynak düzeyinde tutulan bir açıklama tipiyle ayrılır: sınıf dosyasının sabit havuzunda hiç görünmez, koşum sınıf yolunda da hiç gerekmez.

İçindekiler

JVM, Eşzamanlılık ve Başarım kursunun kapanış dersi tek bir soruyu ölçtü: bir adımı hangi iş parçacığının koşturduğu, çağrı yazımından okunamıyordu — yalnız koşturarak görülüyordu. Aynı satır bir iş parçacığında da, başka bir iş parçacığında da aynı yazımdı; koşturan tarafı belirleyen şey kaynak metnin dışındaydı.

Bu ders aynı sorunun bir katman dışını soruyor. Bir Java programı yalnız bir sınıf yoluna karşı değil, iki ayrı sınıf yoluna karşı yaşar: derlendiği yol ve koştuğu yol. Kaynak metin — hatta derlenmiş sınıf dosyasının kendisi — bu iki yolun ne içermesi gerektiğini söylemez; söyleyen şey kaynağın dışında duran bir bildirimdir. Derleyicinin bir tipi görmesi için gereken şey ile çalışma zamanının aynı tipi bulması için gereken şey aynı küme değildir, ve bu ayrımı üreten aracın kendisi bu dersin ve konunun sorusu.

Aracın adı yazılmıyor — hangi ürünün, hangi söz diziminin kullanıldığı bu dersin sorusu değil. Sorulan şey şu: birkaç satırlık bir bildirimden bir derleme aracı ne kadarını kendisi türetiyor, ve türettiği şey neden kaynakta hiç görünmeyen iki ayrı listeye bölünüyor?

Kursun üç konusu da üçüncü taraf araç adlarıyla anılan alanlar; bu yüzden derleme aracının kendisi burada bir davranış olarak kuruluyor, bir ürün olarak değil. Ölçülen şey bir bildirimden bir evre dizisi türeten kural ve o evrelerin sonunda ortaya çıkan iki sınıf yolu — hangi ürünün hangi bayrağı kabul ettiği değil. Standart kitaplığın kendisi bu modelin gerçek çalışan parçasını sağlıyor: derleyici, program içinden ToolProvider ile çağrılıyor ve gerçekten derliyor.

Derleme Aracı Neyi Türetiyor

Bir bildirimin türettiği şey tek bir liste değil, iki ayrı şeydir: bir evre dizisi (hangi sırayla ne yapılacağı) ve o evrelerin içinde kurulan sınıf yolları (her evrenin hangi dizinlere baktığı). Bu iki şey birbirine karıştırılmamalı. “Kaynakları derle” evresi tek bir derleme görevi çalıştırıyor: derleyiciyi bir kez çağırıyor. “Derleme sınıf yolunu kur” evresi ise bildirimdeki her bağımlılığı tek tek gözden geçiren bir görev çalıştırıyor — kapsamı derleme ya da ikisi olan her bağımlılığı listeye ekliyor, kapsamı yalnız koşum olanı atlıyor. Aynı evre, “koşum sınıf yolunu kur”, aynı bağımlılık listesini bu kez tersinden süzüyor. Evrenin kendisi bir tane, ama içindeki derleme görevi bildirimin uzunluğuna göre değişen sayıda iş yapıyor — bu, aşağıdaki ölçümde ayrı ayrı görülecek iki sayının (bildirilen öge, türetilen evre) neden birbirine eşit çıkmayabileceğini de açıklıyor.

Bildirimden Türeyen Evre Dizisi

Bir derleme aracına verilen girdi adım adım bir yordam değil, bir bildirimdir (declaration): hangi kaynak dizininin derleneceği, hangi bağımlılıkların hangi bağımlılık kapsamıyla (dependency scope) kullanılacağı. Aracın kendisi bu bildirimden bir derleme yaşam döngüsü (build lifecycle) türetir — sabit sırayla işleyen bir evre (phase) dizisi. Her evre bir ya da daha çok derleme görevi (task) çalıştırır; burada yalnız evrelerin kendisi ölçülüyor, görevlerin içeriği değil.

Aşağıdaki Bildirim sınıfı bir bağımlılığı yalnız adı ve kapsamıyla tutuyor; kapsam üç değerden birini alıyor: yalnız derlemede gerekli, yalnız koşumda gerekli, ya da ikisinde birden.

  • DR1. Derleme aracı burada bir model olarak kuruluyor; hiçbir gerçek ürünün adı, hedef adı, komut satırı bayrağı ya da bildirim söz dizimi kullanılmıyor. Evre adları ve bildirim biçimi bu dersin kendi kararı.
  • DR2. Bildirilen öge sayısı, bildirimdeki her satırın — kaynak dizini bir, her bağımlılık bir — tek öge sayıldığı bir toplam. Bir bağımlılığın kapsamı öge sayısını değiştirmiyor.
  • DR3. Türetilen evre dizisi sabit dört evreden oluşuyor ve bağımlılık sayısından ya da bildirimin yazılış sırasından etkilenmiyor; sıra aracın kendi kuralı, bildirimin tercihi değil.
// Bildirim.java — derleme aracina verilen bildirim: kaynak dizini adi ve bagimliliklarin kapsam etiketi
import java.util.*;

public class Bildirim {
    record Bagimlilik(String ad, String kapsam) {}

    final String kaynakAdi;
    final List<Bagimlilik> bagimliliklar;

    Bildirim(String kaynakAdi, List<Bagimlilik> bagimliliklar) {
        this.kaynakAdi = kaynakAdi;
        this.bagimliliklar = bagimliliklar;
    }

    int bildirilenOgeSayisi() {
        return 1 + bagimliliklar.size();
    }
}
// Evreler.java — bildirimden turetilen evre dizisi: sira bildirimin yazilis sirasina bakmaz
import java.util.*;

public class Evreler {
    static List<String> turet(Bildirim b) {
        List<String> dizi = new ArrayList<>();
        dizi.add("temizle");
        dizi.add("kaynaklari derle");
        dizi.add("derleme sinif yolunu kur");
        dizi.add("kosum sinif yolunu kur");
        return dizi;
    }
}

Evreler.turet bildirimin içeriğine hiç bakmadan dört evre üretiyor: temizleme, kaynak derleme, derleme sınıf yolunu kurma, koşum sınıf yolunu kurma. Bildirimdeki bağımlılık sayısı bir, iki ya da on olsun, dönen dizinin uzunluğu değişmiyor — çünkü ölçülen şey bağımlılıkların kendisi değil, aracın onlardan ne türettiği.

Aynı Bildirimden İki Sayı, İki Sınıf Yolu

Ölçüm iki ayrı bildirimle başlıyor: aynı iki bağımlılık, biri ileri sırada yazılmış, öbürü ters sırada. Etiket yalnız derlemede gerekli olarak, Servis yalnız koşumda gerekli olarak işaretleniyor. Etiket, saklama politikası (retention policy) kaynak düzeyinde tutulan bir açıklama (annotation) tipi: derleyici onu tip denetiminde kullanıyor, ama sınıf dosyasına hiçbir iz bırakmıyor. Servis’e erişimse tam ters yoldan kuruluyor: Program onu hiç doğrudan adlandırmıyor, yalnız bir ad dizgisiyle arıyor — bu yüzden derleme sınıf yoluna hiç girmesi gerekmiyor.

  • DR4. Etiket, saklama politikası kaynak düzeyinde tutulan bir açıklama tipi; derleyici tip denetiminde kullanır, sınıf dosyasına hiçbir referans bırakmaz.
  • DR5. Servis’e erişim doğrudan tip referansıyla değil, ada göre aranan bir çağrıyla kuruluyor; bu yüzden derleme sınıf yoluna hiç girmiyor.
  • DR6. Derleme sınıf yolu ile koşum sınıf yolu iki ayrı dizin listesi olarak kuruluyor; ikisi arasında ortak hiçbir dizin yok — Etiket’in dizini yalnız derlemede, Servis’in dizini yalnız koşumda veriliyor.
// AracModeli.java — bildirimden turetilen evre dizisi bildirim sirasindan bagimsizdir, ve tek bildirimden iki sinif yolu cikar
import java.io.*;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class AracModeli {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path kaynak) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        arg.add(kaynak.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static boolean sabitHavuzundaAdiGeciyorMu(Path sinifDosyasi, String ad) throws IOException {
        ClassModel model = ClassFile.of().parse(sinifDosyasi);
        for (PoolEntry e : model.constantPool()) {
            if (e instanceof ClassEntry ce && ce.asInternalName().equals(ad)) return true;
        }
        return false;
    }

    public static void main(String[] args) throws Exception {
        var etiket = new Bildirim.Bagimlilik("Etiket", "derleme");
        var servis = new Bildirim.Bagimlilik("Servis", "kosum");

        Bildirim ileriSira = new Bildirim("kaynak", List.of(etiket, servis));
        Bildirim geriSira = new Bildirim("kaynak", List.of(servis, etiket));

        List<String> evreIleri = Evreler.turet(ileriSira);
        List<String> evreGeri = Evreler.turet(geriSira);

        System.out.println("-- evre dizisi, bildirim sirasindan bagimsiz --");
        System.out.println("ileri sirali bildirimden turetilen evreler: " + evreIleri);
        System.out.println("geri sirali bildirimden turetilen evreler : " + evreGeri);
        System.out.println("iki evre dizisi ayni mi                   : " + evreIleri.equals(evreGeri));
        System.out.println("bildirilen oge sayisi                     : " + ileriSira.bildirilenOgeSayisi());
        System.out.println("turetilen evre sayisi                     : " + evreIleri.size());

        Path kok = Files.createTempDirectory("arac");
        Path etiketD = kok.resolve("etiket"), servisD = kok.resolve("servis"), programD = kok.resolve("program");

        Path sEtiket = yaz(kok.resolve("kaynak"), "Etiket",
                "import java.lang.annotation.*;\n"
              + "@Retention(RetentionPolicy.SOURCE)\n"
              + "public @interface Etiket { String deger(); }");
        Path sServis = yaz(kok.resolve("kaynak"), "Servis",
                "public class Servis { public static String calistir() { return \"servis calisti\"; } }");
        Path sProgram = yaz(kok.resolve("kaynak"), "Program",
                "@Etiket(deger = \"ornek\")\n"
              + "public class Program {\n"
              + "  public static void main(String[] a) throws Exception {\n"
              + "    Class<?> s = Class.forName(\"Servis\");\n"
              + "    String r = (String) s.getMethod(\"calistir\").invoke(null);\n"
              + "    System.out.println(\"servis (yansima): \" + r);\n"
              + "  }\n"
              + "}");

        derle(etiketD, List.of(), sEtiket);
        derle(servisD, List.of(), sServis);

        List<Path> derlemeSinifYolu = List.of(etiketD);
        boolean programDerlendi = derle(programD, derlemeSinifYolu, sProgram);
        System.out.println();
        System.out.println("-- tek bildirimden iki sinif yolu --");
        System.out.println("Program, derleme sinif yoluna (yalniz Etiket) karsi derlendi mi   : " + programDerlendi);
        System.out.println("Program.class sabit havuzunda Etiket'e referans var mi            : "
                + sabitHavuzundaAdiGeciyorMu(programD.resolve("Program.class"), "Etiket"));

        List<Path> kosumSinifYolu = List.of(programD, servisD);
        URL[] url = new URL[kosumSinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = kosumSinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, AracModeli.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName("Program", true, yukleyici);
            System.out.println("Program, kosum sinif yoluna (Etiket yok, Servis var) karsi kosuyor mu:");
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
        }
    }
}
-- evre dizisi, bildirim sirasindan bagimsiz --
ileri sirali bildirimden turetilen evreler: [temizle, kaynaklari derle, derleme sinif yolunu kur, kosum sinif yolunu kur]
geri sirali bildirimden turetilen evreler : [temizle, kaynaklari derle, derleme sinif yolunu kur, kosum sinif yolunu kur]
iki evre dizisi ayni mi                   : true
bildirilen oge sayisi                     : 3
turetilen evre sayisi                     : 4

-- tek bildirimden iki sinif yolu --
Program, derleme sinif yoluna (yalniz Etiket) karsi derlendi mi   : true
Program.class sabit havuzunda Etiket'e referans var mi            : false
Program, kosum sinif yoluna (Etiket yok, Servis var) karsi kosuyor mu:
servis (yansima): servis calisti

Çıktının ilk yarısı bildirim sırasının evre dizisini etkilemediğini gösteriyor: ileri sıralı ve geri sıralı bildirimden türeyen evre dizileri birebir aynı, equals karşılaştırması doğru veriyor. Bildirilen öge sayısı 3 (bir kaynak dizini, iki bağımlılık), türetilen evre sayısı 4 — iki sayı birbirinden bağımsız, biri öbürünü belirlemiyor. Aracın türettiği şey bildirimin uzunluğuna değil, kendi kuralına bağlı.

İkinci yarı asıl iddiayı ölçüyor. Program, yalnız Etiketin bulunduğu bir derleme sınıf yoluna karşı derleniyor ve derleme başarılı — derleyici Etiketi tip denetiminde kullandı ama sınıf dosyasına hiçbir iz bırakmadı; sabit havuzunda Etiket adı hiç geçmiyor, bu satır sınıf dosyası okunarak doğrulanıyor, tahmin edilmiyor. Sonra aynı derlenmiş Program, tamamen farklı bir koşum sınıf yoluna karşı çalıştırılıyor: bu yolda Etiket hiç yok, ama Servis var. Program hatasız koşuyor ve Servisi yansımayla buluyor. Derleyicinin gördüğü ad kümesi (Etiket, Program) ile çalışma zamanının bulduğu ad kümesi (Servis, Program) örtüşmüyor — aynı tek bildirimden, aynı tek derlenmiş sınıftan, iki ayrı sınıf yolu çıktı.

Bu iki sonuç aynı satırda birlikte okunmalı. Sabit havuzu denetimi olumsuz bir kanıt veriyor: Etiket orada değil, dolayısıyla koşum sınıf yolunun onu içermesi gerekmiyor — bu, tahmine değil doğrudan sınıf dosyasının kendisine dayanıyor. Koşum denemesi ise olumlu bir kanıt veriyor: Servis gerçekten koşum sınıf yolunda bulunuyor ve gerçekten çağrılıyor. İki kanıt birlikte, derleme sınıf yolu ile koşum sınıf yolunun aynı bildirimden çıkan iki ayrı küme olduğunu, birinin öbürünün alt kümesi ya da üst kümesi olmak zorunda olmadığını gösteriyor.

Sınırlayıcı Ölçüm: Sıra Bir Kural, Bir Tercih Değil

Yukarıdaki ölçümün ilk satırı bir sınır koyuyor: evre sırasının bildirim sırasından bağımsız çıkması bir gözlem değil, aracın modelinin bir sonucu. Evreler.turet bildirimin bagimliliklar listesine hiç bakmıyor; yalnız bildirimin var olduğunu biliyor. Bu yüzden aynı bağımlılık kümesi yazılış sırası tersine çevrilerek verildiğinde bile evre dizisi değişmiyor — sıra bir tercih olsaydı, iki bildirimden iki ayrı dizi çıkardı.

Bu ayrım önemli, çünkü bildirim ile evre dizisi arasındaki ilişki bir eşleme değil, bir kural uygulamasıdır. Bildirimdeki bir satırı silmek ya da eklemek türetilen evrelerin içeriğini değiştirmiyor, yalnız o evrelerin içindeki derleme görevlerinin kapsamını değiştiriyor — örneğin derleme sınıf yolunu kurma evresi artık bir bağımlılık daha içeriyor. Evre sırasının kendisi sabit kalıyor.

Aynı kural bildirimin uzunluğundan da bağımsız. Ölçümdeki bildirim iki bağımlılık taşıyordu; bildirim beş, on ya da hiç bağımlılık taşımasaydı da Evreler.turet yine dört evre dönerdi, çünkü yöntemin gövdesi bağımlılık listesini hiç okumuyor. Bu, aracın bir yorumlayıcı değil bir kural uygulayıcı olduğunu gösteriyor: girdi ne kadar değişirse değişsin, evre kümesi aynı kalıyor, değişen yalnız her evrenin içinde koşan derleme görevinin ne kadar veriyle çalıştığı. Sonraki derste ölçülecek altı tip de bu sabit evre kümesinin içinden geçecek — “derleme sınıf yolunu kur” ve “koşum sınıf yolunu kur” evrelerinin her ikisi de aynı altı bağımlılığı görecek, ama her biri onlardan farklı bir alt kümeyi seçecek.

Sıradaki ders bu ayrımı derinleştiriyor: bağımlılığın kapsamı yalnız o evrenin içindeki listeyi değil, iki sınıf yolunun içeriğini de belirliyor — ve o belirleme, bu derste görülen iki ayrı kümenin (biri derlemede, biri koşumda gerekli olan) tam ölçüsünü verecek.

Özet

  • Bir derleme aracı, bir bildirimden sabit bir evre dizisi türetir; sıra bildirimin yazılış sırasından bağımsızdır ve aracın kendi kuralıdır.
  • Bildirilen öge sayısı (bildirimdeki satır sayısı) ile türetilen evre sayısı ayrı kavramlardır; biri öbürünü belirlemez — burada 3 bildirilen ögeden 4 evre türer.
  • Aynı bildirimden iki ayrı sınıf yolu çıkar: derleme sınıf yolu ve koşum sınıf yolu; bir tipin hangisinde gerekli olduğu, tipin kaynakta nasıl kullanıldığına bağlıdır (doğrudan referans mı, ada göre arama mı).
  • Saklama politikası kaynak düzeyinde tutulan bir açıklama tipi (Etiket), derleyicinin gördüğü ama sınıf dosyasının hiç taşımadığı bir bağımlılık örneğidir; bu, sabit havuzu okunarak doğrudan doğrulanır.

Sonraki Adım

Bu derste bir bağımlılığın hangi sınıf yolunda gerekli olduğu iki örnekle — biri derlemede, biri koşumda — gösterildi. Ama altı ayrı bağımlılık tipi, aralarında doğrudan bağımlılık ve geçişli bağımlılık, tek bir arayüz ve onun iki gerçekleştirimi bir arada bulunduğunda tablo çok daha karmaşık bir hâl alıyor. Sıradaki ders bağımlılık kapsamının kendisini ölçüyor: altı tipin kaçı yalnız derlemede, kaçı yalnız koşumda, kaçı ikisinde birden gerekiyor — ve iki sınıf yolu ayrıldığında ortaya çıkan dört kusurdan kaçının hiç ses çıkarmadığı.

İ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