İçeriğe geç
academia.sh

Ders 13 / 15

Ürün Çeşitleri

Ürün çeşidi ile derleme türünün çarpımından doğan derleme varyantlarının, varyant süzgecinin ve kod kümesi önceliğinin modeli.

Yayın Güncelleme

Bu derste

  • Ürün çeşidi, çeşit boyutu ve derleme varyantı kavramlarını ayırır
  • Derleme varyantı kümesinde bulunan bir varyantın tam, bulunmayan ya da süzgeçle dışlananın karşılıksız kaldığını ölçer
  • Birden çok çeşit boyutunda varyant adının boyut sırasıyla kurulduğunu gösterir
  • Çeşit kod kümesinin ana kod kümesine üstün geldiğini, aynı kaynak kod sınıfının iki kümede bulunmasının çakışma olduğunu sınırlayıcı ölçümle doğrular
İçindekiler

Önceki ders derleme sisteminin bağımlılık sürümlerini nasıl çözdüğünü ölçtü. Derleme sistemi aynı kaynaktan tek bir uygulama üretmek zorunda değildir. Ücretsiz ve ücretli sürümler, farklı pazarlar için paketler ya da farklı sunucuya bağlanan derlemeler aynı kod tabanından çıkar. Bunu ürün çeşitleri sağlar. Bu ders hangi derleme varyantlarının üretildiğini ve bir varyant istendiğinde platformun ne verdiğini ö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 }}")
    }
}

Çeşitler ve Varyantlar

Ürün çeşidi (product flavor) bir uygulamanın aynı kod tabanından türeyen adlandırılmış bir sürümüdür. Çeşitler bir çeşit boyutuna (flavor dimension) bağlanır: boyut, çeşitlerin hangi eksende ayrıştığını söyler. free ve paid çeşitleri bir ücret boyutunda durur. Derleme türleri debug ve release ise başka bir eksendir. Derleme sistemi bu eksenlerin çarpımını alır; her bileşime derleme varyantı (build variant) denir. İki çeşit ve iki derleme türü dört varyant verir: freeDebug, freeRelease, paidDebug, paidRelease. Aşağıdaki parça bu tanımı ve bir varyantı üretimden çıkaran süzgeci gösterir.

// modul derleme betigi
android {
    flavorDimensions += "tier"
    productFlavors {
        create("free") { dimension = "tier" }
        create("paid") { dimension = "tier" }
    }
}

androidComponents {
    beforeVariants { variant ->
        if (variant.name == "paidDebug") variant.enable = false
    }
}

Betik platform araçlarının okuduğu bir betiktir ve çalıştırılmaz. Süzgeç, adı verilen varyantı üretimden çıkarır; o varyant derlenemez ve seçilemez. Model, platformun bu kuralını kurar. Bu parçada süzgeç paidDebug varyantını dışarıda bırakır.

Varyant Kümesi

Model varyant adını çeşit adı ile derleme türü adının birleşiminden kurar: çeşit olduğu gibi, derleme türü baş harfi büyük. Birden çok boyut varsa çeşitler boyutların sırasıyla birleşir. İstenen varyant kümede varsa ve süzgeçle dışlanmamışsa verilen, istenenin aynısıdır. Süzgeçle dışlanmışsa ya da kümede yoksa varyant verilmez.

// Variants.kt — varyant kumesi modeli
fun variantNames(dimensions: List<List<String>>, buildTypes: List<String>): List<String> {
    var combos = listOf("")
    for (dimension in dimensions) {
        combos = combos.flatMap { prefix ->
            dimension.map { flavor ->
                if (prefix.isEmpty()) flavor else prefix + flavor.replaceFirstChar { it.uppercase() }
            }
        }
    }
    return combos.flatMap { combo -> buildTypes.map { type -> combo + type.replaceFirstChar { it.uppercase() } } }
}

fun resolveVariant(label: String, wanted: String, variants: List<String>, filtered: Set<String>): Ask {
    val outcome = when {
        wanted in filtered -> Outcome.Refused("suzgecle-dislandi")
        wanted in variants -> Outcome.Given(wanted)
        else -> Outcome.Refused("varyant-yok")
    }
    return Ask(label, wanted, outcome)
}

İlk ölçüm tek boyutlu iki çeşit ve iki derleme türü kurar ve paidDebug süzgeçle dışlanmıştır. Dört istek sorulur: geliştirme için bir varyant, dağıtım için bir varyant, süzgeçle dışlanan varyant ve hiç tanımlanmamış bir derleme türü.

// Main.kt — varyant kumesi ve suzgec
fun main() {
    val variants = variantNames(listOf(listOf("free", "paid")), listOf("debug", "release"))
    println(variants.joinToString())
    val filtered = setOf("paidDebug")
    val asks = listOf(
        resolveVariant("calistir", "freeDebug", variants, filtered),
        resolveVariant("yayin", "paidRelease", variants, filtered),
        resolveVariant("suzulmus", "paidDebug", variants, filtered),
        resolveVariant("tanimsiz", "freeStaging", variants, filtered),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
freeDebug, freeRelease, paidDebug, paidRelease
calistir: istenen=freeDebug | verilen=freeDebug -> tam
yayin: istenen=paidRelease | verilen=paidRelease -> tam
suzulmus: istenen=paidDebug | neden=suzgecle-dislandi -> karsiliksiz
tanimsiz: istenen=freeStaging | neden=varyant-yok -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 2

Varyant adı kümede olduğu gibi yazılınca verilir; olmayan ad yerine başka bir varyant verilmez. Bu yüzden varyant ölçümünde ikame çıkmaz: varyant ya vardır ya yoktur. paidDebug kümede variants listesinde durur ama süzgeç onu dışlar; kural iki karşılıksızın nedenini ayırmaz, nedeni modelin taşıdığı dizgi söyler.

AP1. Model varyant adını çeşit ve derleme türünden birleştirir. Gerçek derleme sisteminin adlandırmasında ve boyut önceliğinde başka ayrıntılar vardır; ölçüm bunları kapsamaz.

AP2. Süzgeç yalnız varyantın adına bakar. Gerçek süzgeç varyantın başka özelliklerini de okuyabilir.

Birden Çok Boyut

İkinci ölçümde iki boyut vardır: ücret boyutu (free, paid) ve pazar boyutu (global, local). Derleme türleriyle çarpım sekiz varyant verir. Çeşitler boyut sırasıyla birleşir: ücret önce, pazar sonra. Üç istek sorulur: sıraya uyan bir ad, sıra ters çevrilmiş bir ad ve süzgeçle dışlanan bir ad.

// Main.kt — iki boyut
fun main() {
    val variants = variantNames(
        listOf(listOf("free", "paid"), listOf("global", "local")),
        listOf("debug", "release"),
    )
    println(variants.size)
    val filtered = setOf("paidLocalDebug")
    val asks = listOf(
        resolveVariant("sirali", "freeLocalDebug", variants, filtered),
        resolveVariant("ters", "localFreeDebug", variants, filtered),
        resolveVariant("suzulmus", "paidLocalDebug", variants, filtered),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
8
sirali: istenen=freeLocalDebug | verilen=freeLocalDebug -> tam
ters: istenen=localFreeDebug | neden=varyant-yok -> karsiliksiz
suzulmus: istenen=paidLocalDebug | neden=suzgecle-dislandi -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 2

Aynı iki çeşit ters sırayla yazılınca varyant bulunamadı: ad boyut sırasının sonucudur. Kümenin boyutu çeşit sayılarının ve derleme türü sayısının çarpımıdır: 2 × 2 × 2.

Boyut Sırası ve Çarpım

Çeşit boyutlarının sırası iki yerde işe yarar. Birincisi varyant adıdır: ad boyut sırasıyla kurulur, yukarıdaki ölçüm bunu gösterdi. İkincisi çakışmadır: iki boyuttaki çeşit aynı ayarı tanımlarsa hangisinin üstün geleceği boyut sırasından çıkar. Bu ders ikinci kuralı ölçmez; yalnız birincisini ölçer. Aşağıdaki betik parçası iki boyutu sıralı olarak bildirir.

// modul derleme betigi
android {
    flavorDimensions += listOf("tier", "market")
    productFlavors {
        create("free") { dimension = "tier" }
        create("paid") { dimension = "tier" }
        create("global") { dimension = "market" }
        create("local") { dimension = "market" }
    }
}

Varyant sayısı boyutlardaki çeşit sayılarının ve derleme türü sayısının çarpımıdır. Üçüncü bir boyut eklemek sayıyı katlar: iki çeşitli üç boyut ve iki derleme türü on altı varyant verir. Her varyant ayrı derlenir, ayrı test edilir ve ayrı imzalanır; bu yüzden çarpım hesabı tasarım aşamasında yapılır. Süzgeç gereksiz bileşimleri baştan ayıklamanın yoludur. Platformun çeşit tanımında bir çeşidin bir boyuta bağlanmasının zorunlu olup olmadığı gibi ayrıntılar bu modelin dışındadır.

AP4. Model boyut sırasını çeşit listesinin sırasından alır; derleme ayarındaki boyut bildirimini okumaz.

Kod Kümesi Öncelik Sırası

Bir varyant derlenirken ona giren kod kümeleri bir sırayla dizilir. Model sırayı en düşük öncelikten en yükseğe kurar: önce ana küme, sonra çeşitlerin kümeleri, sonra derleme türünün kümesi, en sonda bileşik varyantın kümesi. Çeşitler arasında önce bildirilen boyutun çeşidi daha yüksek önceliktedir, bu yüzden listeye ters sırayla girer. Bir dosya birden çok kümede bulunuyorsa sonuncu küme kazanır. layersFor sırayı kurar, resolveLayered dosyayı sondan başa doğru arar.

// Layers.kt — kod kumesi sirasi modeli
fun layersFor(flavors: List<String>, buildType: String): List<String> {
    val variant = flavors.joinToString("") { it.replaceFirstChar { c -> c.uppercase() } }
        .replaceFirstChar { it.lowercase() } + buildType.replaceFirstChar { it.uppercase() }
    return listOf("main") + flavors.reversed() + listOf(buildType, variant)
}

fun resolveLayered(path: String, wanted: String, order: List<String>, sets: Map<String, Set<String>>): Ask {
    val owner = order.reversed().firstOrNull { path in (sets[it] ?: emptySet()) }
    val outcome = if (owner == null) Outcome.Refused("dosya-yok") else Outcome.Given(owner)
    return Ask(path, wanted, outcome)
}

İlk ölçümde free çeşidi ve release derleme türü var. Sıra beş kümeden kurulur ve üç dosya sorulur: yalnız ana kümede olan, hem ana hem çeşit kümesinde olan ve hem ana hem bileşik varyant kümesinde olan.

// Main.kt — kod kumesi sirasi
fun main() {
    val order = layersFor(listOf("free"), "release")
    println(order.joinToString())
    val sets = mapOf(
        "main" to setOf("res/logo.xml", "res/title.xml", "res/banner.xml"),
        "free" to setOf("res/title.xml"),
        "freeRelease" to setOf("res/banner.xml"),
    )
    val asks = listOf(
        resolveLayered("res/logo.xml", "main", order, sets),
        resolveLayered("res/title.xml", "main", order, sets),
        resolveLayered("res/banner.xml", "main", order, sets),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
main, free, release, freeRelease
res/logo.xml: istenen=main | verilen=main -> tam
res/title.xml: istenen=main | verilen=free -> ikame
res/banner.xml: istenen=main | verilen=freeRelease -> ikame
tam: 1
ikame: 2
bekleyen: 0
karsiliksiz: 0

Dosya hangi kümede bulunursa bulunsun, sonuç yalnız dosyayı taşıyan en öncelikli kümenin adıdır. Bu yüzden ana kümeden istenen dosya başka kümeden verilince ikame sayılır. İkinci ölçüm bir kenar durumudur: hiçbir kümede bulunmayan dosya ve iki çeşit kümesinde bulunan dosya.

// Main.kt — kenar durumlari
fun main() {
    val order = layersFor(listOf("free", "local"), "debug")
    println(order.joinToString())
    val sets = mapOf(
        "free" to setOf("res/promo.xml"),
        "local" to setOf("res/promo.xml"),
    )
    val asks = listOf(
        resolveLayered("res/promo.xml", "local", order, sets),
        resolveLayered("res/missing.xml", "main", order, sets),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
main, local, free, debug, freeLocalDebug
res/promo.xml: istenen=local | verilen=free -> ikame
res/missing.xml: istenen=main | neden=dosya-yok -> karsiliksiz
tam: 0
ikame: 1
bekleyen: 0
karsiliksiz: 1

Aynı dosya iki çeşit kümesinde bulunduğunda model önce bildirilen boyutun çeşidini seçti. local kümesinden istenen dosya free kümesinden verildi: ikame. Platform da bu durumda boyutların bildirim sırasına bakar ve önce bildirilen boyutun çeşidini kazandırır; iki çeşit kümesinde bulunan kaynak dosyası bu yüzden çakışma sayılmaz.

AP5. Model platformun dört basamaklı sırasını kurar: varyant, derleme türü, çeşitler, ana küme. Kaynakların birleştirilmesindeki ayrıntılı kurallar ölçülmedi; ölçüm yalnız modelin bu sırayı uyguladığını gösterir.

Kod Kümeleri

Her çeşit, kendi kaynak kodunu ve kaynaklarını taşıyan bir kod kümesine (source set) sahiptir. Dizinler çeşit ve varyant adlarıyla kurulur:

  • src/main her varyanta girer.
  • src/free ve src/paid ilgili çeşitlerde girer.
  • src/paidRelease yalnız o varyanta girer.

Aynı varyantın kod kümeleri derlemede birleşir ve bir dosya birden çok kümede bulunursa çeşit kümesi ana kümeye üstün gelir. Kaynak dosyalar için bu kural böyle işler. Kaynak kod sınıfları için ise iki kümede aynı sınıf bulunursa sonuç çakışmadır ve derleme durur. Model bu iki ayrımı tek işlevde kurar.

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

resolveSourceFile bir dosya yolunu ana küme ve çeşit kümesiyle karşılaştırır. Yalnız çeşit kümesinde bulunan dosya çeşitten, yalnız ana kümede bulunan ana kümeden gelir. İkisinde de bulunan kaynak dosyasında çeşit kazanır; ikisinde de bulunan .kt sınıfı çakışmadır.

// Sources.kt — kod kumesi onceligi modeli
class SourceSet(val name: String, val files: Set<String>)

fun resolveSourceFile(path: String, wanted: String, main: SourceSet, flavor: SourceSet): Ask {
    val inMain = path in main.files
    val inFlavor = path in flavor.files
    val outcome = when {
        inMain && inFlavor && path.endsWith(".kt") -> Outcome.Refused("sinif-cakismasi")
        inFlavor -> Outcome.Given(flavor.name)
        inMain -> Outcome.Given(main.name)
        else -> Outcome.Refused("dosya-yok")
    }
    return Ask(path, wanted, outcome)
}

Ölçümde dört dosya vardır. Yalnız çeşit kümesinde olan reklam sınıfı, yalnız ana kümede olan ortak görsel, iki kümede bulunan metin kaynağı ve iki kümede bulunan ödeme sınıfı.

// Main.kt — kod kumesi onceligi
fun main() {
    val main = SourceSet("main", setOf("Billing.kt", "res/logo.xml", "res/strings.xml"))
    val free = SourceSet("free", setOf("Ads.kt", "Billing.kt", "res/strings.xml"))
    val asks = listOf(
        resolveSourceFile("Ads.kt", "free", main, free),
        resolveSourceFile("res/logo.xml", "main", main, free),
        resolveSourceFile("res/strings.xml", "main", main, free),
        resolveSourceFile("Billing.kt", "main", main, free),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
Ads.kt: istenen=free | verilen=free -> tam
res/logo.xml: istenen=main | verilen=main -> tam
res/strings.xml: istenen=main | verilen=free -> ikame
Billing.kt: istenen=main | neden=sinif-cakismasi -> karsiliksiz
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 1

Metin kaynağı ana kümeden istendi ve çeşit kümesinden verildi: ikame. Aynı çift için Billing.kt hiçbir şey verilmeden çakışma olarak kaldı. Kural iki sonucu yalnız verilen değerden ayırır; ikamenin gerçek derlemede kasıtlı bir üstüne yazma, çakışmanın ise bir hata olduğunu kural bilmez.

AP3. Model yalnız bir ana küme ile tek bir çeşit kümesini karşılaştırır. Varyant ve derleme türü kümelerinin birbirine göre önceliği ölçülmedi.

Özet

  • Bu dersin ölçütü: karşılama; istenen varyantın ya da dosyanın olduğu gibi verilip verilmediği, girdinin değişmesi değil. Bu dersin karşılaması: ikame.
  • Ürün çeşidi aynı kod tabanından türeyen adlandırılmış bir sürümdür; çeşit boyutu çeşitlerin ayrıştığı eksendir.
  • Derleme varyantı çeşitler ile derleme türlerinin çarpımıdır; ad boyut sırasıyla kurulur.
  • Kümede bulunan varyant tam verilir; kümede olmayan ya da süzgeçle dışlanan varyant karşılıksızdır.
  • Çeşit kod kümesi ana kümeye üstün gelir; kaynak dosyada bu ikame, aynı kaynak kod sınıfında çakışmadır.

Sonraki Adım

Derleme varyantları hazır olunca uygulamanın çalıştırılıp denenmesi gerekir. Sonraki ders geliştirme ortamını, emülatörü, aygıt bağlantısını ve hata ayıklama araçlarını ele alır.

İ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