skip to content

Walk through the FileChange objects returned by InputChanges.getFileChanges: what does each ChangeType mean and what information does a FileChange carry?

level: juniorimportance: should knowfreq 32%

answer

  1. Iterable<FileChange>
  2. ChangeType ADDED/MODIFIED/REMOVED
  3. getFile / normalizedPath / fileType
  4. rename = REMOVED + ADDED
  5. filter FileType.DIRECTORY

basics

~10 s

getFileChanges returns an iterable of FileChange. Each has a ChangeType (ADDED, MODIFIED, or REMOVED) and the file plus its normalized path, telling you what happened to that one input file.

solid answer

~40 s

`inputChanges.getFileChanges(inputProperty)` returns an `Iterable<FileChange>`. Each `FileChange` describes one input file's change relative to the previous run via `getChangeType()`: **ADDED** (a new input file appeared), **MODIFIED** (an existing input file's content changed), or **REMOVED** (an input file was deleted). A `FileChange` also exposes `getFile()` (the `java.io.File`), `getNormalizedPath()` (path relative to the input root after normalization — stable for computing output locations), and `getFileType()` (`FILE`, `DIRECTORY`, or `MISSING`). You react per type: regenerate outputs for ADDED/MODIFIED, delete the derived output for REMOVED, and usually skip `DIRECTORY` entries. For REMOVED entries the underlying file no longer exists, so rely on `normalizedPath` rather than reading `getFile()`.

code

groovy · 8 lines
groovy
inputChanges.getFileChanges(inputDir).each { change ->
    if (change.fileType == FileType.DIRECTORY) return
    def out = outputDir.file(change.normalizedPath).get().asFile
    switch (change.changeType) {
        case ChangeType.REMOVED: out.delete(); break
        default: out.parentFile.mkdirs(); out.text = change.file.text.toUpperCase()
    }
}

go deeper

for a junior

Recall the three ChangeType values and that each FileChange names one file.

for a middle

Describe the FileChange accessors and the rename = REMOVED+ADDED behavior.

for a senior

Explain normalizedPath's role in deterministic output mapping and DIRECTORY/MISSING handling.

for a principal

Generally delegated; focus on ensuring teams encode robust per-type handling as a reviewed pattern.

## The shape of the data Calling `getFileChanges(prop)` — where `prop` is one of your `@Incremental` file inputs — yields an `Iterable<org.gradle.work.FileChange>`. Each element answers 'what happened to this one file?'. ### ChangeType - **ADDED** — the file is present now and was not in the previous snapshot. Generate its output. - **MODIFIED** — the file existed before and its (normalized) content changed. Regenerate its output. - **REMOVED** — the file was in the previous snapshot and is gone now. Delete the output it had produced. Its `getFile()` points at a path that no longer exists. Note there is no separate 'renamed' type: a rename surfaces as a REMOVED of the old path plus an ADDED of the new path. ### FileChange accessors - `getFile(): File` — the concrete file (valid for ADDED/MODIFIED; stale for REMOVED). - `getChangeType(): ChangeType` — the enum above. - `getNormalizedPath(): String` — the file's path relative to the input root, after the input's normalization (e.g. `@PathSensitive(RELATIVE)`). Use this to map input -> output deterministically. - `getFileType(): FileType` — `FILE`, `DIRECTORY`, or `MISSING`. Directory entries are often reported when the input is a directory tree; most actions ignore them and act only on `FILE`. ## Typical dispatch ```kotlin changes.getFileChanges(sources).forEach { c -> if (c.fileType == FileType.DIRECTORY) return@forEach val out = outputDir.file(c.normalizedPath + ".out").get().asFile when (c.changeType) { ChangeType.ADDED, ChangeType.MODIFIED -> { out.parentFile.mkdirs(); transform(c.file, out) } ChangeType.REMOVED -> out.delete() } } ``` ## Gotchas - **MISSING file type** can appear for a REMOVED change — don't `readText()` such a file. - **Directory changes**: when you add/remove a whole folder you may get a DIRECTORY-typed change plus per-file changes; filtering on `FileType.FILE` keeps logic simple. - **Renames are two events** — handle REMOVED cleanup so the old output doesn't linger after a rename.

  • How does a file rename appear in the change list?
    As two FileChange entries: a REMOVED for the old path and an ADDED for the new path — there is no dedicated rename type.
  • Why prefer normalizedPath over getFile().getPath() for mapping outputs?
    normalizedPath is relative to the input root and stable across machines/normalization, and it remains meaningful even for REMOVED files whose getFile() is gone.

saying these in an interview costs you the question

  • Believing there is a RENAMED ChangeType.
  • Calling readText/length on a REMOVED change's file (it may be MISSING).

context