skip to content

How do you change or add source and resource directories for a source set, for example to support a non-standard project layout?

level: middleimportance: should knowfreq 50%

answer

  1. java / resources are SourceDirectorySet
  2. srcDir = append, srcDirs = replace
  3. register generated-sources dir
  4. wiring a task provider tracks dependency
  5. resources handled by processResources

basics

~10 s

Configure the source set's java.srcDirs (or resources.srcDirs) inside the sourceSets block. Use srcDir(...) to add a directory or assign srcDirs = listOf(...) to replace the defaults.

solid answer

~30 s

Each source set exposes typed `SourceDirectorySet`s named `java` and `resources`. To add a directory you call `srcDir("path")`; to replace the convention entirely you assign `srcDirs = setOf("path")`. For example, to support a Maven-incompatible layout where Java lives under `src`, you write `sourceSets["main"].java.srcDirs("src")`. Adding rather than replacing is usually safer because it preserves the default `src/main/java`. Source directories are also where generated code is registered — pointing `java.srcDir` at a generated-sources folder makes that code part of compilation and incremental-build tracking. The `resources` set works identically for non-class files copied by `processResources`.

code

kotlin · 11 lines
kotlin
val genTask = tasks.register<MyCodeGen>("generateApi") {
    outputDir.set(layout.buildDirectory.dir("generated/api"))
}

sourceSets {
    named("main") {
        // Adding a task's output: Gradle infers generateApi must run before compileJava
        java.srcDir(genTask.flatMap { it.outputDir })
        resources.srcDir("src/main/proto-resources")
    }
}

go deeper

for a junior

Know that you can point a source set at a different directory via the sourceSets block.

for a middle

Distinguish add vs. replace, and know java/resources are SourceDirectorySets.

for a senior

Explain wiring generated output through providers for correct incremental builds and task dependency inference.

for a principal

Standardize layout conventions across modules and reason about migration cost when changing source roots org-wide.

## SourceDirectorySet — the model behind directories Inside a source set, `java` and `resources` are `SourceDirectorySet` objects. A `SourceDirectorySet` is a `FileCollection` plus configuration for its root directories, include/exclude filters, and an output directory. You manipulate it through: - **`srcDir(Object)`** / **`srcDirs(Object...)`** — *append* one or more directories to the existing set. Paths are resolved relative to the project directory. - **`setSrcDirs(Iterable)`** / assigning `srcDirs = ...` — *replace* the whole set, discarding the convention default. - **`include(...)` / `exclude(...)`** — Ant-style filters on which files participate. ## Adding vs. replacing ```kotlin sourceSets { named("main") { // ADD a directory, keep src/main/java too java.srcDir("src/generated/java") // REPLACE the defaults entirely resources.setSrcDirs(listOf("config", "assets")) } } ``` Adding is the common case for **generated sources** (e.g. output of an annotation processor or a code-gen task): register the generation task's output directory via `java.srcDir(codegenTask)` so Gradle tracks the task dependency automatically and recompiles when generated code changes. Replacing is used when you adopt a **non-standard layout** — for instance a legacy project where all Java sits directly under `src/`. ## Why use the model rather than hardcoding paths Because `srcDir` accepts task providers and `Provider`s, wiring generated output through it gives you correct **incremental build** and **task dependency** inference for free — Gradle knows the codegen task must run before `compileJava`. Manually copying files would break up-to-date checking. ## Resources The `resources` `SourceDirectorySet` behaves the same way; its files are handled by `processResources`, which copies them into `build/resources/<set>` without compilation.

  • Why is wiring a generation task's output via `srcDir(provider)` better than just adding the literal path?
    Passing the task provider lets Gradle infer the task dependency and track inputs/outputs, so `compileJava` automatically runs codegen first and stays up-to-date. A literal path string carries no dependency information.
  • What's the difference between `srcDir` and assigning `srcDirs`?
    `srcDir(...)` appends to the existing directories (keeping defaults); assigning `srcDirs = ...` (or `setSrcDirs`) replaces them entirely, dropping the convention.

saying these in an interview costs you the question

  • Replacing `srcDirs` when you only meant to add one, accidentally dropping `src/main/java`.
  • Adding a generated-sources directory as a raw string and then wondering why codegen doesn't run before compilation — no task dependency was inferred.

context