İçeriğe geç
academia.sh

Ders 12 / 15

Derleme Sistemi

Derleme sisteminin bağımlılıkları ve sürüm kataloğunu nasıl okuduğunun, geçişli bağımlılıkta en yüksek sürümün seçilmesinin ve katı sürüm kısıtının modeli.

Yayın Güncelleme

Bu derste

  • Proje ayar betiği, modül derleme betiği ve sürüm kataloğunun işlevlerini ayırır
  • Geçişli bağımlılıkta düşük sürümü isteyen modülün ikame, yükseği isteyen modülün tam karşılandığını ölçer
  • Sürüm kataloğunun tüm modüllere aynı sürümü verdiğini gösterir
  • Katı sürüm kısıtı uyuşmazlığının karşılıksız kaldığını ve sürüm sıralamasının sayısal olduğunu sınırlayıcı ölçümle doğrular
İçindekiler

Önceki ders kaynakların yapılandırmaya göre nasıl seçildiğini ölçtü. Bildirim dosyası, kaynaklar ve kod tek başına çalışan bir uygulama değildir; bunları bir araya getirip kurulabilir bir pakete dönüştüren, derleme sistemidir. Bu ders derleme sisteminin ana parçalarını tanıtır ve bir modülün istediği bağımlılık sürümünün, başka modüllerin istekleriyle çakıştığında ne olduğunu ölçer.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki 3/2/2/1 sayısı burada değiştirilmez.

// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}

Derleme Sisteminin Parçaları

Bir proje üç ayrı yazılı belge ile kurulur. Proje ayar betiği projenin adını ve hangi modüllerden oluştuğunu söyler. Her modülün kendi modül derleme betiği vardır: modülün derleme API düzeyini, en düşük ve hedef API düzeyini, derleme türlerini ve bağımlılıklarını tanımlar. Sürüm kataloğu ise bağımlılıkların adlarını ve sürümlerini tek yerde toplar. Aşağıdaki iki parça proje ayar betiğini ve bir modül derleme betiğini gösterir; ikisi de platform araçlarının okuduğu betiklerdir ve çalıştırılmaz.

// proje ayar betigi
rootProject.name = "Notes"
include(":app", ":data")
// modul derleme betigi
android {
    namespace = "com.example.notes"
    compileSdk = 34
    defaultConfig {
        minSdk = 24
        targetSdk = 34
    }
    buildTypes {
        release { isMinifyEnabled = true }
    }
}

dependencies {
    implementation(project(":data"))
    implementation(libs.notesCore)
}

Betik iki derleme türü (build type) bilir: geliştirme sırasında kullanılan debug ve dağıtım için hazırlanan release. Her tür kendi ayarlarını taşır; burada release için kod küçültme açılmıştır. Bağımlılık bildirimi iki biçimde yazılabilir: başka bir modülü project(":data") ile ya da sürüm kataloğundaki bir kitaplığı libs.notesCore ile. Sürüm kataloğu ayrı bir dosyadır.

[versions]
notesCore = "1.4"

[libraries]
notesCore = { module = "com.example:notes-core", version.ref = "notesCore" }

Derleme sistemi her adımı bir derleme adımı (build task) olarak çalıştırır: kaynakları işler, kodu derler, paketler. Adımlar birbirine bağımlıdır ve sistem sırayı bağımlılıklardan çıkarır. Bu derste ölçülen derleme adımlarının sırası değil, bağımlılık sürümlerinin çözümlenmesidir.

Geçişli Bağımlılıkta Sürüm Seçimi

Bir modül bir kitaplığı doğrudan ister; o kitaplık başka kitaplıklara bağlıdır. Bunlara geçişli bağımlılık (transitive dependency) denir. Aynı kitaplık yolda birden çok kez, farklı sürümlerle istenebilir. Derleme sistemi tek bir sürüm seçer ve model bu seçimi en yüksek sürüm olarak kurar. Her istek ayrı bir sorudur: istenen, modülün yazdığı sürümdür; verilen, çözümlemenin seçtiği sürümdür.

// Resolver.kt — surum cozumlemesi modeli
class Request(val from: String, val library: String, val version: String, val strict: Boolean = false)

fun compareVersions(a: String, b: String): Int {
    val left = a.split(".").map { it.toInt() }
    val right = b.split(".").map { it.toInt() }
    for (i in 0 until maxOf(left.size, right.size)) {
        val diff = left.getOrElse(i) { 0 } - right.getOrElse(i) { 0 }
        if (diff != 0) return diff
    }
    return 0
}

fun signOf(n: Int): Int = if (n < 0) -1 else if (n > 0) 1 else 0

fun resolveDependency(requests: List<Request>): List<Ask> {
    val strictVersions = requests.filter { it.strict }.map { it.version }.toSet()
    val highest = requests.map { it.version }.reduce { best, next ->
        if (compareVersions(next, best) > 0) next else best
    }
    return requests.map { request ->
        val outcome = when {
            strictVersions.size > 1 -> Outcome.Refused("kati-surum-uyusmazligi")
            strictVersions.size == 1 -> Outcome.Given(strictVersions.single())
            else -> Outcome.Given(highest)
        }
        Ask("${request.from}>${request.library}", request.version, outcome)
    }
}

İlk ölçümde iki kitaplık vardır. notes-core kitaplığını uygulama modülü 1.2 sürümüyle, veri modülü 1.4 sürümüyle ister. net-client kitaplığını yalnız uygulama modülü ister.

// Main.kt — en yuksek surum secilir
fun main() {
    val core = resolveDependency(listOf(
        Request("app", "notes-core", "1.2"),
        Request("data", "notes-core", "1.4"),
    ))
    val net = resolveDependency(listOf(Request("app", "net-client", "3.0")))
    val asks = core + net
    for (ask in asks) ask.report()
    tally(asks)
}
app>notes-core: istenen=1.2 | verilen=1.4 -> ikame
data>notes-core: istenen=1.4 | verilen=1.4 -> tam
app>net-client: istenen=3.0 | verilen=3.0 -> tam
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 0

Veri modülü istediği sürümü aldı. Uygulama modülü 1.2 istedi ve 1.4 aldı: yüksek sürüm bir ikamedir, çünkü verilen istenenden farklıdır. Kural bu sürümün daha iyi ya da kötü olduğunu söylemez; yalnız eşit olmadığını söyler. Tek isteği olan kitaplıkta çözümlenecek bir şey yoktur ve tam çıkar.

AP1. Model yalnız en yüksek sürümü seçme kuralını kurar. Gerçek çözümleyici aralık sürümleri, sürüm ağırlıkları ve dışlamalar gibi başka girdileri de okur.

AP2. Sürüm dizgileri yalnız noktayla ayrılmış sayılardan oluşur. Ön sürüm ekleri ve harf içeren sürümler modelin dışındadır.

Sürüm Kataloğu

Sürüm kataloğunun amacı, aynı kitaplığın her modülde ayrı ayrı yazılmasını önlemektir. Modüller sürümü kendileri yazmaz; katalogdaki adı kullanır ve sürüm tek yerden gelir. Aşağıdaki ölçüm bunu iki durumda sorar. Katalogla üç modül de aynı sürümü ister. Katalog olmadan her modül elle bir sürüm yazmış ve üç sürüm birbirinden ayrışmıştır.

// Main.kt — katalogla ve katalogsuz
fun main() {
    val withCatalog = resolveDependency(listOf(
        Request("app", "ui-kit", "2.0"),
        Request("data", "ui-kit", "2.0"),
        Request("sync", "ui-kit", "2.0"),
    ))
    val withoutCatalog = resolveDependency(listOf(
        Request("app", "ui-kit", "1.8"),
        Request("data", "ui-kit", "1.9"),
        Request("sync", "ui-kit", "2.0"),
    ))
    val asks = withCatalog + withoutCatalog
    for (ask in asks) ask.report()
    tally(asks)
}
app>ui-kit: istenen=2.0 | verilen=2.0 -> tam
data>ui-kit: istenen=2.0 | verilen=2.0 -> tam
sync>ui-kit: istenen=2.0 | verilen=2.0 -> tam
app>ui-kit: istenen=1.8 | verilen=2.0 -> ikame
data>ui-kit: istenen=1.9 | verilen=2.0 -> ikame
sync>ui-kit: istenen=2.0 | verilen=2.0 -> tam
tam: 4
ikame: 2
bekleyen: 0
karsiliksiz: 0

Katalogla üç istek de tam çıktı. Katalogsuz durumda en yüksek sürümü isteyen modül tam, ötekiler ikame olur. Sürümü katalogdan okumanın sonucu, ayrışma ihtimalini ortadan kaldırmasıdır; ölçü yine yalnız verilenle istenenin eşitliğine bakar.

Proje ve Modül Ayrımı

Proje ayar betiği ile modül derleme betiği iki farklı düzeyde çalışır. Proje düzeyinde bağımlılıkların nereden indirileceği ve hangi modüllerin projeye girdiği belirlenir. Modül düzeyinde o modülün neye bağlı olduğu ve nasıl derleneceği belirlenir. Aşağıdaki parça proje düzeyinde bir bağımlılık deposu bildirimini gösterir.

// proje ayar betigi
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Depo bildirimi proje düzeyinde olduğu için her modül aynı yerlerden bağımlılık çözer. Bir modülün betiği ayrıca kendi derleme türlerini, debug ve release, tanımlayabilir. Derleme türü kod küçültme, hata ayıklanabilirlik ve imzalama gibi ayarları taşır. Çeşit boyutu yoksa derleme varyantı derleme türüyle aynı adı taşır; sonraki ders çeşitlerin bu çarpıma girdiğini ölçer.

Bağımlılık Yapılandırmaları

Bir bağımlılık hangi sınıf yolunda görüneceğiyle birlikte bildirilir. Bildirimin ilk sözcüğü yapılandırmadır (configuration). implementation bağımlılığı modülün derlemesine ve çalışmasına katar ama modülü kullanan başka modüllere göstermez. api aynı bağımlılığı kullanan modüllerin derleme sınıf yoluna da sokar. compileOnly yalnız derlemeye girer, çalışan uygulamaya girmez. runtimeOnly yalnız çalışmaya girer. testImplementation yalnız test derlemesine girer. Model her yapılandırmaya dört sınıf yolundan hangilerine ulaştığını söyleyen bir küme bağlar: derleme, çalışma, tüketici ve test.

// Config.kt — bagimlilik yapilandirmalari modeli
val reach: Map<String, Set<String>> = mapOf(
    "implementation" to setOf("derleme", "calisma"),
    "api" to setOf("derleme", "calisma", "tuketici"),
    "compileOnly" to setOf("derleme"),
    "runtimeOnly" to setOf("calisma"),
    "testImplementation" to setOf("test"),
)

fun resolveConfiguration(configuration: String, classpath: String): Ask {
    val paths = reach[configuration]
    val outcome = when {
        paths == null -> Outcome.Refused("yapilandirma-yok")
        classpath in paths -> Outcome.Given(classpath)
        else -> Outcome.Refused("sinif-yoluna-girmez")
    }
    return Ask(configuration, classpath, outcome)
}

İlk ölçümde aynı modüle ait bir kitaplığın çalışma anında bulunması istenir. Üç yapılandırma sorulur.

// Main.kt — calisma sinif yolu
fun main() {
    val asks = listOf("implementation", "runtimeOnly", "compileOnly").map { resolveConfiguration(it, "calisma") }
    for (ask in asks) ask.report()
    tally(asks)
}
implementation: istenen=calisma | verilen=calisma -> tam
runtimeOnly: istenen=calisma | verilen=calisma -> tam
compileOnly: istenen=calisma | neden=sinif-yoluna-girmez -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1

Derleme zamanında bir arayüz kitaplığı gerekiyor ama çalışma anında başka bir paket onu sağlıyorsa compileOnly doğru seçimdir. Yanlış seçim çalışma anında sınıfın bulunamamasına yol açar. Model bunu karşılıksız olarak gösterir. İkinci ölçüm kenar durumlarını sorar: tüketici modülün derleme sınıf yolunda görünme, test sınıf yolunda görünme ve hiç tanımlanmamış bir yapılandırma.

// Main.kt — tuketici, test ve bilinmeyen
fun main() {
    val asks = listOf(
        resolveConfiguration("api", "tuketici"),
        resolveConfiguration("implementation", "tuketici"),
        resolveConfiguration("testImplementation", "derleme"),
        resolveConfiguration("debugImplementation", "calisma"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
api: istenen=tuketici | verilen=tuketici -> tam
implementation: istenen=tuketici | neden=sinif-yoluna-girmez -> karsiliksiz
testImplementation: istenen=derleme | neden=sinif-yoluna-girmez -> karsiliksiz
debugImplementation: istenen=calisma | neden=yapilandirma-yok -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 3

api ile implementation aynı kitaplığı aynı modülde derler ama tüketicilere görünürlükleri ayrılır. Bu ayrım uzun vadede önemlidir: gereksiz api kullanımı bir modüldeki değişikliğin onu kullanan her modülün yeniden derlenmesine yol açabilir. Model bu maliyeti ölçmez, yalnız erişimi ölçer.

AP3. Model yapılandırma adını küçük bir sabit tablodan okur. Gerçek derleme sisteminde yapılandırmalar derleme türüne göre çoğalır (örneğin yalnız bir derleme türüne özel olanlar) ve modül kendi yapılandırmasını tanımlayabilir.

Sınırlayıcı Ölçüm

Model iki yerde sınırlıdır. Birincisi sürümün sıralanmasıdır. 1.10 sürümü 1.9 sürümünden yüksektir, çünkü sürüm sayısal parçalarla karşılaştırılır. Aynı iki dizgi alfabetik sıralanırsa sonuç tersine döner. compareVersions sayısal sıralamayı, dizgi compareTo ise alfabetik sıralamayı uygular; ilk iki satır ikisinin işaretini basar. İkincisi katı sürüm kısıtıdır (strictly): iki modül aynı kitaplık için farklı katı sürüm yazdıysa çözümleme bir sürüm seçemez.

// Main.kt — siralama ve kati kisit
fun main() {
    println("sayisal: ${signOf(compareVersions("1.10", "1.9"))}")
    println("dizgi: ${signOf("1.10".compareTo("1.9"))}")
    val ordering = resolveDependency(listOf(
        Request("app", "log-tools", "1.9"),
        Request("data", "log-tools", "1.10"),
    ))
    val strict = resolveDependency(listOf(
        Request("app", "crypto-lib", "2.0", strict = true),
        Request("sync", "crypto-lib", "2.1", strict = true),
    ))
    val asks = ordering + strict
    for (ask in asks) ask.report()
    tally(asks)
}
sayisal: 1
dizgi: -1
app>log-tools: istenen=1.9 | verilen=1.10 -> ikame
data>log-tools: istenen=1.10 | verilen=1.10 -> tam
app>crypto-lib: istenen=2.0 | neden=kati-surum-uyusmazligi -> karsiliksiz
sync>crypto-lib: istenen=2.1 | neden=kati-surum-uyusmazligi -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 2

Sayısal sıralamada işaret pozitiftir: 1.10 yüksektir. Dizgi sıralamasında işaret negatiftir: aynı iki dizgi için tersi çıkar. Bu yüzden app>log-tools isteği ikame oldu ve data>log-tools tam kaldı. Model sayısal sıralamayı kurar; ölçüm yalnız modelin bu sıralamayı uyguladığını gösterir. Katı kısıtta iki katı sürüm farklı olduğundan çözümleme hiçbir sürüm vermedi ve her iki istek de karşılıksız çıktı. Kural uyuşmazlığın nedenini görmez; nedeni modelin taşıdığı kati-surum-uyusmazligi dizgisi söyler.

AP4. Model katı kısıtı yalnız iki katı sürüm birbirinden farklıyken karşılıksız sayar. Tek bir katı sürüm olduğunda o sürümü verir; katı sürümün başka bir isteği düşürüp düşüremeyeceği gibi ayrıntılar ölçülmez.

Özet

  • Bu dersin ölçütü: karşılama; çözümlemenin verdiği sürümün modülün istediği sürüme eşit olup olmadığı. Bu dersin karşılaması: ikame.
  • Proje ayar betiği modülleri sayar, modül derleme betiği bir modülün ayarlarını ve bağımlılıklarını tanımlar, sürüm kataloğu adları ve sürümleri tek yerde toplar.
  • debug ve release derleme türleridir; her türün kendi ayarları vardır.
  • Geçişli bağımlılıkta model en yüksek sürümü seçer: düşük sürümü isteyen ikame, yükseği isteyen tam karşılanır.
  • Sürüm kataloğu tüm modüllere aynı sürümü verdiği için ayrışmayı önler.
  • Sürüm sıralaması sayısaldır; katı sürüm kısıtı uyuşmazlığı karşılıksızdır.

Sonraki Adım

Derleme sistemi aynı kaynaktan birden çok uygulama üretebilir: ücretsiz ve ücretli sürümler, ya da farklı pazarlar için paketler. Sonraki ders ürün çeşitlerini ve bunların derleme türleriyle çarpımından doğan derleme varyantlarını ölçer.

İ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