Table of Contents

Task caching 🔗

Deder caches task outputs on disk so tasks whose inputs haven't changed don't re-run. This is separate from Scala incremental compilation: the compile task itself always re-invokes Zinc, and Zinc does incremental compilation internally.

How caching behaves 🔗

Cached vs always-run tasks 🔗

The lists below are accurate as of deder 0.3.4. To verify against current source, grep server/src for CachedTaskBuilder (cached) and = TaskBuilder (always-run).

Cached (skip when inputs unchanged):

Task What it produces
sourceFiles List of source file paths
dependencies Direct module dependencies
allDependencies Dependencies including transitive
mandatoryDependencies Dependencies that must be present
compileClasspath Classpath used for compilation
allClassesDirs Class output directories across modules
compilerDeps Compiler dependency resolution
compilerJars Resolved compiler JARs
scalacPlugins Scalac plugin JARs
javacAnnotationProcessors Annotation processor JARs
scalaSemanticdbVersion Resolved SemanticDB version
runClasspath Classpath used at runtime
mainClasses Discovered main classes
finalMainClass Selected main class
testClasses Test class discovery
fastLinkJs Scala.js fast link output
nativeLink Scala Native link output
jar Module JAR
allJars Aggregated JARs
sourcesJar Sources JAR
javadocJar Javadoc JAR
assembly Uber JAR
finalManifestSettings Resolved JAR manifest settings
moduleDepsPomSettings POM dep settings
publishArtifacts Artifact set to publish

Always runs:

Task What it does
generatedSources Runs source generators
classes Resolves module class output directory
semanticdbDir Resolves SemanticDB output directory
compile Invokes Zinc
run Runs the module
runMain Runs a specified main class
runMvnApp Runs a Maven-application entry point
fix Runs Scalafix
fixCheck Runs Scalafix in check mode
test Runs tests
testJs Runs Scala.js tests
testNative Runs Scala Native tests
publishLocal Publishes to local Ivy repository
publish Publishes to remote repository

compile is "always runs" from deder's perspective, but Zinc skips unchanged sources internally — so unchanged compilations are still cheap even without deder-level caching.

Where cached data lives 🔗

Every cached task writes a metadata.json (plus any task-specific artifacts) under:

.deder/out/<module-id>/<task-name>/metadata.json

metadata.json stores the task's stored value, inputsHash, and outputHash.

What invalidates a cached task 🔗

Clearing the cache 🔗

deder clean -m <module-id> removes .deder/out/<module>/ entirely (all cached artifacts and metadata for that module). Multiple -m flags clean multiple modules:

deder clean -m mymodule
deder clean -m mod1 -m mod2

deder clean -t <task-name> removes .deder/out/<module>/<task>/ for the specified task across all modules. Combine -m and -t to target a specific task on specific modules:

deder clean -t compile
deder clean -m mymodule -t test
deder clean -m mod% -t compile%

Wildcard patterns (%) are supported for both -m and -t flags.

How caching works 🔗

Two task kinds 🔗

The hash chain 🔗

SourceFileTask (CachedTask leaf) ──► outputHash = hash(file contents)
         │
         ▼
compileClasspath (CachedTask) ──────► inputsHash = hash(dep outputHashes)outputHash = hash(result)compile (TaskImpl) ─────────────────► always runs
                                       outputHash = hash(classes dir)
         │
         ▼
assembly (CachedTask) ──────────────► skips if compile's outputHash unchanged

Hashable[T] derivation 🔗

Explicit instances live in server/src/ba/sake/deder/Hashable.scala: Int, String, Boolean, os.Path, Option[T], Seq[T], Map[K,V]. A low-priority given derives Hashable[T] from JsonRW[T] by hashing the JSON string, so any config type is automatically hashable. Explicit instances take priority — os.Path hashes file/directory contents (recursively, with leaf names bound to child hashes) rather than the path string.

Known limitations 🔗

⬅️ Architecture Files layout ➡️