skip to content

How can you add extra files (README, license, config) to the distZip/distTar archive without touching the application plugin's defaults?

level: seniorimportance: should knowfreq 30%

answer

  1. distributions.main.contents is a CopySpec
  2. from(...)/into(...) is additive over bin+lib
  3. src/dist auto-included in root
  4. shared spec => zip/tar/install stay identical
  5. files copied at execution, not config time

basics

~10 s

Use distributions.main.contents { ... }, which is a CopySpec. from("...") adds files (e.g. into a etc/ or root folder) on top of the default bin/lib that the application plugin already supplies.

solid answer

~40 s

The application plugin populates `distributions.main.contents` (a `CopySpec`) with the jar, runtime libs, and start scripts. You **augment**, not replace, by adding to that same CopySpec: `distributions { main { contents { from("README.md"); into("docs") { from("src/dist/docs") } } } }`. Everything you add lands in both `distZip` and `distTar` (and the `installDist` output) because all three share that CopySpec. A convention is the `src/dist` directory: by default the application plugin maps `src/dist` into the distribution root, so dropping files there ships them with zero config. For per-archive-only additions you could configure the `distZip` task's CopySpec directly, but that breaks parity between zip, tar, and install — usually undesirable. Remember `contents` runs lazily, so reference files via the CopySpec, not by eagerly reading them at configuration time.

code

kotlin · 10 lines
kotlin
distributions {
    main {
        contents {
            from("README.md")
            from("LICENSE")
            into("etc") { from("src/main/resources/app.conf") }
        }
    }
}
// Or simply drop files under src/dist/ — auto-included in the archive root.

go deeper

for a junior

Know you can add files via distributions.main.contents { from(...) }.

for a middle

Use into(...) for subfolders and know the src/dist convention; understand additions are additive.

for a senior

Explain the shared CopySpec backing zip/tar/install, lazy execution-time copying, and filter/expand for templating.

for a principal

Standardize sidecar content (license, NOTICE, ops configs) across services via a convention plugin and keep zip/tar/install parity.

## CopySpec: the unifying abstraction A **`CopySpec`** declares *what files go where* — a tree of `from(...)`/`into(...)`/`include`/`exclude`/`rename`/`filter` rules. Every archive and copy task in Gradle (`Zip`, `Tar`, `Copy`, `Sync`) is driven by one. The distribution plugin exposes `distributions.main.contents` as the single CopySpec backing `distZip`, `distTar`, **and** `installDist`. That shared spec is why all three outputs stay identical. ## Adding files (the right way) ```kotlin distributions { main { distributionBaseName.set("myapp") contents { from("README.md") // -> <root>/README.md from("LICENSE") into("config") { // -> <root>/config/... from("src/main/resources/app.conf") } } } } ``` Because the application plugin already added `bin/` and `lib/` to this same CopySpec, your `from(...)` calls are **additive** — you don't lose the start scripts or jars. ## The `src/dist` convention The application plugin automatically includes the contents of `src/dist` in the distribution root. So a file at `src/dist/etc/logback.xml` ships at `<root>/etc/logback.xml` with no build code at all. Prefer this for static sidecar files. ## Lazy / configuration-cache friendliness `contents { }` configures a CopySpec; the files are read at **execution** time, not while the build script evaluates. Don't do `from(file("x").readText())` — pass paths/providers and let Gradle copy. You can also `filter`/`expand` to do token replacement (e.g. stamp a version into a config file) at copy time. ## Per-archive vs. shared If you truly need something only in the zip, you can configure `tasks.distZip { from(...) }` — but then zip, tar, and the unpacked install diverge, which surprises consumers. The idiomatic choice is to keep everything in `distributions.main.contents` so parity holds. ## Note on scope Defining entirely separate, arbitrary-content distributions (not the app's main one) is a sibling concern; here we're augmenting the application's main distribution that distZip/distTar package.

  • Why does adding to distributions.main.contents affect installDist too?
    distZip, distTar, and installDist are all driven by the same shared CopySpec, so any addition appears in all three.
  • What's the zero-config way to ship a static file in the archive root?
    Place it under `src/dist/...`; the application plugin maps `src/dist` into the distribution root automatically.
  • How would you stamp the project version into a config file inside the archive?
    Use the CopySpec's `expand`/`filter` (e.g. `expand("version" to project.version)`) so substitution happens at copy/execution time.

saying these in an interview costs you the question

  • Replacing `contents` wholesale and thereby dropping the default bin/lib.
  • Eagerly reading file contents at configuration time instead of declaring CopySpec rules (breaks laziness / configuration cache).
  • Adding files only to distZip and assuming tar/install match.

context