How would you roll out dependency locking across a large multi-module build, and what scope and policy decisions matter?
answer
- convention plugin applies policy once
- scope: all vs runtime/compile only
- STRICT mode forbids unlocked
- CI: no-diff after --write-locks
- pair with dependency verification
basics
~10 sDecide which configurations to lock (often all resolvable ones), generate per-project lockfiles, commit them, and enforce regeneration in CI. Use STRICT mode to forbid unlocked resolution, and define a standard --update-locks workflow for upgrades.
solid answer
~50 sAt scale, locking is a governance decision, not just a flag. Key choices: **scope** — `lockAllConfigurations()` is broad and simple, but you may exclude tooling/buildscript configurations or use per-configuration `activateDependencyLocking()` to lock only runtime/compile classpaths that actually ship. **Consistency** — every subproject gets its own `gradle.lockfile`; you need a single repeatable command that resolves across all projects so generation isn't piecemeal. **Enforcement** — enable `LockMode.STRICT` so a lockable configuration with no recorded state fails rather than silently resolving unlocked; optionally add a CI check that the committed lockfiles match a fresh `--write-locks` (no diff). **Upgrade workflow** — standardize `--update-locks g:m` for targeted/security bumps and `--write-locks` for dependency changes, with the lockfile diff as the review artifact. Pair locking with **dependency verification** (checksums/signatures) for full supply-chain reproducibility. The trade-off is review friction on every dependency change versus guaranteed, auditable reproducibility.
code
kotlin · 6 lines// buildSrc/src/main/kotlin/locking-conventions.gradle.kts
dependencyLocking {
lockAllConfigurations()
lockMode.set(LockMode.STRICT) // resolving without lock state fails
}
// every module applies: plugins { id("locking-conventions") }go deeper
Out of depth; at most note locking should be consistent across modules and committed.
Mention per-project lockfiles, committing them, and a standard regeneration command.
Discuss scope choices, STRICT mode, a CI no-diff guard, and the targeted vs broad upgrade workflow.
Frame as governance: convention-plugin policy, STRICT enforcement, CI verification, pairing with dependency verification, and the velocity-vs-reproducibility trade-off.
## Treat locking as policy In a single module, locking is a convenience. Across dozens of modules and a shared platform, it becomes a reproducibility/supply-chain control that needs explicit decisions. ## 1. Scope: what to lock - **Broad**: `dependencyLocking { lockAllConfigurations() }` applied via a convention plugin so every module behaves identically. Simplest to reason about. - **Targeted**: lock only the configurations whose resolution actually ships or affects builds (e.g. `runtimeClasspath`, `compileClasspath`) via `resolutionStrategy.activateDependencyLocking()`. Avoids churn on test-tooling or annotation-processor configs you don't care to pin. - Apply through a **convention/precompiled plugin** so the policy is one place, not copy-pasted into every `build.gradle.kts`. ```kotlin // buildSrc convention plugin dependencyLocking { lockAllConfigurations() lockMode.set(LockMode.STRICT) } ``` ## 2. Generation at scale Each project has its own `gradle.lockfile`. You need a deterministic command that resolves *every* subproject's locked configurations, e.g. a custom aggregating task or running `dependencies --write-locks` per project. Document it so anyone can regenerate identically. ## 3. Enforcement - **STRICT mode** (`lockMode.set(LockMode.STRICT)`) makes resolving a lockable configuration with no lock state a failure — no accidental unlocked builds. - **CI guard**: run `--write-locks` in a throwaway step and fail if `gradle.lockfile` changes, proving the committed locks are current. ## 4. Upgrade workflow (the human process) | Change | Command | Review signal | |---|---|---| | Security patch / single bump | `--update-locks g:m` | tiny, focused diff | | Add/remove/re-version deps | `--write-locks` | broader diff, expected | The lockfile diff in the PR is the audit trail of exactly what moved. ## 5. Combine with dependency verification Locking pins *which versions*; Gradle's **dependency verification** (`gradle/verification-metadata.xml`, checksums/PGP) pins *that the bytes are authentic*. Together they give reproducible-and-trusted resolution — the supply-chain story interviewers probe for. ## Trade-offs - **Pro**: deterministic builds, auditable upgrades, no silent transitive drift. - **Con**: every dependency change requires regenerating and reviewing lockfiles; dynamic versions lose their auto-update convenience (which is the point). Teams sometimes scope locking to release branches only to balance velocity vs. reproducibility.
- How is dependency locking different from dependency verification?Locking pins which versions resolve (reproducibility of the graph). Verification pins the cryptographic checksums/signatures of artifacts (authenticity of the bytes). They are complementary supply-chain controls; you often use both.
- How would CI prove the committed lockfiles are up to date?Run `--write-locks` in a disposable step and fail the build if it produces any change to gradle.lockfile — a clean diff means the checked-in locks match current resolution.
- Why apply locking via a convention plugin rather than per module?It centralizes the policy (scope, STRICT mode) so every module behaves identically and the decision lives in one place, avoiding drift between subprojects.
saying these in an interview costs you the question
- Conflating locking with dependency verification.
- Copy-pasting the locking block into every module instead of a convention plugin.
- Ignoring the per-project nature of lockfiles and assuming one root file.