Skip to content

Gradle resolver can generate cycles with some dependencies #1471

@smocherla-brex

Description

@smocherla-brex

For some dependencies leveraging Gradle Module metadata, the resolver generates a lockfile fine but the maven tree has cycles. Consider the below example

maven.install(
    name = "android_maven_deps",
    artifacts = [
         "androidx.appcompat:appcompat:1.6.1",
        "androidx.constraintlayout:constraintlayout:2.1.4",
        "androidx.navigation:navigation-fragment-ktx:2.6.0",
        "androidx.navigation:navigation-ui-ktx:2.6.0",
        "androidx.core:core:1.10.1",
        "androidx.core:core-ktx:1.10.1",
        "androidx.fragment:fragment-ktx:1.6.1",
        "com.google.android.material:material:1.10.0",
        "junit:junit:4.13.2",
        "androidx.test.espresso:espresso-core:3.5.1",
    ],
    lock_file = "//:maven_install.json",
    repositories = [
        "https://maven.google.com/",
        "https://repo1.maven.org/maven2/",
    ],
    resolver = "gradle",
)

Run REPIN=1 bazel run @android_maven_deps//:pin and then run bazel build @android_maven_deps//:all (or an android library that depends on some of the maven deps), you'll see a cycle like below.

ERROR: /private/var/tmp/_bazel_smocherla/9ba6fe3025d5a24a711a27211d7af8b6/external/rules_jvm_external++maven+my_maven/BUILD:1453:11: in aar_import rule @@rules_jvm_external++maven+my_maven//:androidx_savedstate_savedstate: cycle in dependency graph:
    @@rules_jvm_external++maven+my_maven//:androidx_activity_activity (6c6c5c29ad6bd40b35919e8610f1eb023ea9183d35ae240db579717a51622940)
    @@rules_jvm_external++maven+my_maven//:androidx_activity_activity (9b5881b1424f3fa4e1ca2fb75acbb41b00266413f4d1a59a4ba12bc51ace762b)
.-> @@rules_jvm_external++maven+my_maven//:androidx_savedstate_savedstate (9b5881b1424f3fa4e1ca2fb75acbb41b00266413f4d1a59a4ba12bc51ace762b)
|   @@rules_jvm_external++maven+my_maven//:androidx_savedstate_savedstate_ktx (9b5881b1424f3fa4e1ca2fb75acbb41b00266413f4d1a59a4ba12bc51ace762b)
`-- @@rules_jvm_external++maven+my_maven//:androidx_savedstate_savedstate (9b5881b1424f3fa4e1ca2fb75acbb41b00266413f4d1a59a4ba12bc51ace762b)

Bazel doesn't like cycles for obvious reasons. And neither does gradle atleast in a single resolved graph in a configuration. So this one took some debugging but it appears to happen because of dependencyConstraints in the Gradle metadata of the artifacts. As gradle docs mention:

Dependency constraints function similarly to dependencies, with the key distinction that they do not introduce a dependency themselves

So because of this, the following code in the resolver

for (DependencyResult dep : component.getDependencies()) {
      if (!(dep instanceof ResolvedDependencyResult)) {
        continue;
      }

      ResolvedDependencyResult resolvedDep = (ResolvedDependencyResult) dep;
      ResolvedComponentResult selected = resolvedDep.getSelected();

...

will also include dependency constraints as edges with getDependencies(). Why is that a problem? Consider the dependencies in the error above androidx.savedstate:savedstate and androidx.savedstate:savedstate-ktx. Both of them resolve to 1.2.1 in maven_install.json as seen below.

Image

Now let's check the Gradle Metadata for both.

https://dl.google.com/android/maven2/androidx/savedstate/savedstate/1.2.1/savedstate-1.2.1.module (savedstate)

      "dependencyConstraints": [
        {
          "group": "androidx.savedstate",
          "module": "savedstate-ktx",
          "version": {
            "requires": "1.2.1"
          }
        }
      ],

The above introduces an edge from savedstate to savedstate-ktx

https://dl.google.com/android/maven2/androidx/savedstate/savedstate-ktx/1.2.1/savedstate-ktx-1.2.1.module (savedstate-ktx)

      "dependencyConstraints": [
        {
          "group": "androidx.savedstate",
          "module": "savedstate",
          "version": {
            "requires": "1.2.1"
          }
        }
      ],

The above introduces an edge from savedstate-ktx to savedstate.

Due to this, we now have the cycle seen in the bazel build. The only valid dependency is from savedstate-ktx to savedstate as seen from the dependencies section

   "dependencies": [
        {
          "group": "androidx.savedstate",
          "module": "savedstate",
          "version": {
            "requires": "1.2.1"
          }
        },
        {
          "group": "org.jetbrains.kotlin",
          "module": "kotlin-stdlib",
          "version": {
            "requires": "1.8.10"
          }
        }
      ],

Gradle provides a way to detect if a resolved dependency is a dependency constraint and not an actual dependency with https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/result/DependencyResult.html#isConstraint() which we can use here to eliminate those edges.

Interestingly enough, gradle itself also handles this here https://github.com/gradle/gradle/blob/06bba06808c1d6bf5dd0157ec8ec30a58c877e27/platforms/software/software-diagnostics/src/main/java/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphsRenderer.java#L100 when rendering dependencies and avoid going into cycles.

A similar cycle can be seen if you add androidx.fragment:fragment-ktx:1.6.1 here and regen the lockfile

maven_install(
    name = "regression_testing_gradle",
    artifacts = [
        # https://github.com/bazel-contrib/rules_jvm_external/issues/909
        "androidx.compose.foundation:foundation-layout:1.5.0-beta01",
        # https://github.com/bazel-contrib/rules_jvm_external/issues/909#issuecomment-2019217013
        "androidx.annotation:annotation:1.6.0",
        # https://github.com/bazel-contrib/rules_jvm_external/issues/1409
        "com.squareup.okhttp3:okhttp:4.12.0",

        # https://github.com/bazel-contrib/rules_jvm_external/issues/1471
        "androidx.fragment:fragment-ktx:1.6.1",
    ],
    generate_compat_repositories = True,
    maven_install_json = "//tests/custom_maven_install:regression_testing_gradle_install.json",
    repin_instructions = "Please run `REPIN=1 bazel run @regression_testing_gradle//:pin` to refresh the lock file.",
    repositories = [
        "https://repo1.maven.org/maven2",
        "https://maven.google.com",
    ],
    resolver = "gradle",
)

and build

WARNING: errors encountered while analyzing target '@@_main~maven~regression_testing_gradle//:androidx_compose_ui_ui_util', it will not be built.
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:1850:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_savedstate: cycle in dependency graph:
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx_2_6_1 (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
.-> @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
|   @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
`-- @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:2051:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_savedstate_savedstate: cycle in dependency graph:
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx_2_6_1 (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
.-> @@_main~maven~regression_testing_gradle//:androidx_savedstate_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
|   @@_main~maven~regression_testing_gradle//:androidx_savedstate_savedstate_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
`-- @@_main~maven~regression_testing_gradle//:androidx_savedstate_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:1797:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx: cycle in dependency graph:
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx_2_6_1 (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
.-> @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
|   @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_savedstate (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
`-- @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:1741:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime: cycle in dependency graph:
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx_2_6_1 (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
.-> @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
|   @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_viewmodel_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
`-- @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:1687:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx: cycle in dependency graph:
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx_2_6_1 (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
    @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (47e801383873100ccaaef3b95ef65bfd34a90b019bca16b9564cbba636e352c6)
.-> @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
|   @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
`-- @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx (d2ba34083d5d2b9afc911c8ef1260b5a22a4542c041360241fc8c3e4af3fea72)
ERROR: /private/var/tmp/_bazel_smocherla/536b5c8af4e86fad57fc24f39380ac8a/external/_main~maven~regression_testing_gradle/BUILD:1687:11: in aar_import rule @@_main~maven~regression_testing_gradle//:androidx_lifecycle_lifecycle_runtime_ktx: cycle in dependency graph:

with bazel build @regression_testing_gradle//:pin --keep_going

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions