skip to content

Show the different ways to access a source set (e.g. jvmMain) inside kotlin { sourceSets { } } to declare its dependencies, and explain when each is needed.

level: middleimportance: should knowfreq 55%

answer

  1. typed accessor: jvmMain.dependencies
  2. by getting = existing, lazy
  3. by creating / maybeCreate = new set
  4. getByName = imperative existing
  5. declare targets before configuring sets

basics

~10 s

Modern Kotlin gives you typed accessors like jvmMain.dependencies { }. Older code uses val jvmMain by getting { } or getByName("jvmMain"). You use getting/getByName for custom or not-yet-created source sets.

solid answer

~40 s

Inside kotlin { sourceSets { } } there are three common ways to reach a source set. The newest (Kotlin 1.9.20+) provides direct typed accessors generated for the targets you declared: commonMain.dependencies { }, jvmMain.dependencies { }. Before that you used Gradle's delegated-property accessor val jvmMain by getting { dependencies { } }, which resolves an existing source set lazily by its property name. For source sets you create yourself (custom intermediate sets) you use val desktopMain by creating { } and then wire dependsOn. getByName("jvmMain") { } is the imperative equivalent of getting. The typed accessors only exist once the corresponding target (jvm(), androidTarget()) is declared, because the plugin generates the source sets from targets. So ordering matters: declare targets first, then configure their source sets.

code

kotlin · 8 lines
kotlin
kotlin {
    jvm()
    sourceSets {
        jvmMain.dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") }      // typed
        val commonMain by getting { dependencies { implementation("io.ktor:ktor-client-core:3.0.0") } } // getting
        val desktopMain by creating { dependsOn(commonMain) }                                // creating
    }
}

go deeper

for a junior

Recognizes that you access source sets inside sourceSets { } and add dependencies there.

for a middle

Knows typed accessor vs by getting vs by creating and when each applies.

for a senior

Explains NamedDomainObjectContainer semantics, lazy resolution, and target-before-sourceset ordering.

for a principal

Designs reusable build conventions/plugins around these accessors and avoids fragile ordering pitfalls in large multi-module KMP builds.

## Why there are several forms Source sets in KMP are created by the plugin from the **targets** you declare (`jvm()` creates `jvmMain`/`jvmTest`). The `sourceSets { }` container is a Gradle `NamedDomainObjectContainer`, so it supports the standard delegated-property accessors (`getting`, `creating`) plus the imperative `getByName`/`maybeCreate`. On top of that, recent Kotlin adds **generated typed accessors**. ## The forms ```kotlin kotlin { jvm() androidTarget() sourceSets { // 1) Typed accessor (Kotlin 1.9.20+): cleanest, autocomplete-friendly jvmMain.dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") } // 2) Delegated 'getting': resolves an EXISTING source set lazily by name val androidMain by getting { dependencies { implementation("androidx.core:core-ktx:1.13.0") } } // 3) Imperative getByName: same as getting, no delegate getByName("commonMain").dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") } // 4) 'creating' for a NEW custom (intermediate) source set val desktopMain by creating { dependsOn(getByName("commonMain")) } } } ``` ## getting vs creating vs maybeCreate - `by getting` and `getByName` **require the source set to already exist** — they throw `UnknownDomainObjectException` if not. Use for plugin-generated sets. - `by creating` and `maybeCreate("x")` **create** a source set. Use for custom intermediate source sets (e.g. a `desktopMain` shared by `jvmMain` and `mingwMain`) which you then connect with `dependsOn`. - The **typed accessors** (`jvmMain`, `commonMain`) only appear after the matching target is declared; that is why you put `jvm()` / `androidTarget()` above the `sourceSets { }` block. ## Practical guidance - Prefer typed accessors for built-in source sets — concise and refactor-safe. - Use `by getting`/`getByName` when targeting older Kotlin or when you need a `val` to reference elsewhere (e.g. to `dependsOn`). - `dependencies { }` inside any of these takes the usual configurations: `implementation`, `api`, `compileOnly`, `runtimeOnly`.

  • Why must jvm() be declared before you use the jvmMain typed accessor?
    The plugin generates source sets (and their typed accessors) from the declared targets, so the accessor doesn't exist until the target is registered.
  • What's the difference between by getting and by creating?
    getting fetches an existing source set and fails if it's missing; creating registers a brand-new one, which you use for custom intermediate source sets.

saying these in an interview costs you the question

  • Using by getting for a custom source set that doesn't exist yet (will throw)
  • Configuring jvmMain via typed accessor before declaring jvm() target
  • Confusing creating with getting
  • Thinking getByName creates a missing source set

context