Skip to content

Usage With Core Gradle Locks

Roberto Perez Alcolea edited this page Mar 13, 2024 · 3 revisions

Core Gradle Locks with Nebula Dependency Lock Plugin

Migration from Nebula locks to Core Gradle locks

If you have been using project locks

If you have been using tasks such as generateLock, saveLock, and updateLock, then you can run add the following property to gradle.properties:

systemProp.nebula.features.coreLockingSupport=true

and run the following:

./gradlew migrateToCoreLocks

and you will be migrated to core Gradle locks using your currently locked dependencies as well as any other unlocked dependencies. These may have been floating to lock only specific dependencies, or because they are transitive dependencies, but now they will be locked.

Locked configurations

  • By default, not all configurations are locked. The set of configurations to lock can be see here. They are based on compileClasspath, testCompileClasspath, runtimeClasspath, testRuntimeClasspath, annotationProcessor, testAnnotationProcessor and configurations that contribute to these.
  • Lock all configurations via the property lockAllConfigurations=true

If you have been using global locks

These are not supported by core Gradle locks. You will need to delete the global lock and use project locks.

Caution

Global Locks are not recommended. While Global Locks served a purpose in the past, they are not recommended for use.

Instead of relying on in Global Locks for dependency consistency across a multi-project, we recommend using a combination of Gradle's version catalogs, consistent dependency resolution and project level locks.

Using Core Gradle locks

You can use gradle core locks by using the flags --write-locks and --update-locks with a comma separated list of dependencies to update, such as group1:artifact1,group2:artifact2 along with a task to resolve all project configurations (such as those needed for a multi-project build).

Single-project example of writing locks:

./gradlew dependencies --write-locks

Note: this is used instead of the generateLock and saveLock tasks.

Multiproject example of updating locks:

In the parent build.gradle, add a task to resolve dependencies for all projects. One example might be:

subprojects {
  task dependenciesForAll(type: DependencyReportTask) {}
}

and then invoke that task to resolve all dependencies and update locks:

./gradlew dependenciesForAll --update-locks group1:artifact1,group2:artifact2

Note: this is used instead of the updateLock and saveLock tasks.

Missing a lockfile?

With this plugin on top of Core Gradle locks, we chose not to lock all configurations by default. This means that when updating plugins, the plugin configuration is not automatically locked and the person updating plugins can continue with the workflow. Instead, we default lock configurations that directly contribute to (and include) compileClasspath, testCompileClasspath, runtimeClasspath, testRuntimeClasspath, annotationProcessor, testAnnotationProcessor, as mentioned above.

Opt-in to locking specific configurations that are not included in that above list via a property in gradle.properties:

dependencyLock.additionalConfigurationsToLock=comma,separated,configurations

If you really want every project configuration locked, then add the following property to gradle.properties, and this will lock all configurations:

lockAllConfigurations=true

Changes in behavior

Updating a single dependency with a specific version

Please note that with Nebula's (non-Core Gradle) locking updates, you could specify the exact requested version via the command line.

With Gradle core locking, the locked state is resolved via constraints from build.gradle.

If you'd like an exact version, please add a constraint such as the following to build.gradle and examine your updated lockfile to ensure the desired version is locked:

configurations.all {
  resolutionStrategy {
      force 'test.example:foo:1.0.0'
  }
}

Selective dependency upgrades

When selectively updating locks, this acts as a selective dependency upgrade. The Gradle docs state "In this mode, the existing lock state is still used as input to resolution, filtering out the modules targeted by the update."

What does this mean?

If you had locked a single direct dependency test:a:1.0.0 that brings in other:b:5.0.0, and you update to test:a:1.1.0 that brings in a lower version of a transitive dependency other:b:4.5.0, then your resolved dependencies will be test:a:1.1.0 and the higher version other:b:5.0.0.

If this is problematic, we recommend either resolving all dependencies rather than performing a selective dependency upgrade or forging a new path by updating a dependency and all of its transitives by name (there is not a supported tool for this, and it is not a supported use case).

Dependency Lock Plugin usage with Core Gradle locks

What is this plugin doing on top of core locks? A few things.

  1. Use a default set of configurations to lock, rather than locking every one to care foremost about the project classpaths.
  2. Opt-in to locking specific configurations that are not included in that above list via a property.
  3. Ensure all resolved configurations have done so successfully. For example, core Gradle will provide information that a dependency was not resolved, and not update the lockfiles, which can cause an unexpected state. This behavior will fail the build and require user intervention, instead.

Questions

What about classpath locks?

We are prioritizing using Core Gradle locks for project dependencies before working with classpath locks. Our examples and tests are geared only towards project dependencies at this time.