Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cousier resolution error for Android aar dependencies #3993

Open
0xnm opened this issue Nov 19, 2024 · 10 comments
Open

Cousier resolution error for Android aar dependencies #3993

0xnm opened this issue Nov 19, 2024 · 10 comments

Comments

@0xnm
Copy link
Contributor

0xnm commented Nov 19, 2024

Repro steps:

  • Create a AndroidAppKotlinModule (doesn't really need to be this one, just make sure that artifactTypes has also coursier.Type("aar"), but probably it doesn't matter at the POM resolution stage) with the following content:
def repositoriesTask: Task[Seq[Repository]] = Task.Anon {
  super.repositoriesTask() :+ MavenRepository("https://maven.google.com/")
}

def ivyDeps: T[Agg[Dep]] = Agg(
  ivy"androidx.core:core-ktx:1.15.0",
  ivy"androidx.activity:activity-compose:1.9.3",
  ivy"androidx.compose.ui:ui:1.7.5",
  ivy"androidx.compose.material3:material3:1.3.1"
)
  • Run ivyDepsTree for this module.

The outcome will be an error:

app.ivyDepsTree coursier.error.ResolutionError$ConflictingDependencies: Conflicting dependencies:
androidx.collection:collection:1.0.0 or 1.1.0 or 1.4.0 or 1.4.2 or [1.2.0] wanted by
... <see attached file for the resolved tree>

At the same time if same dependencies are added in a Gradle build script via implementation dependency configuration, everything is resolved correctly (see attached file for the complete resolution tree).

I'm not sure where the issue is, probably rather on the Coursier side?

Gradle is using the following conflict resolution strategy by default (link):

Gradle’s conflict resolution strategy, which defaults to selecting the highest version, selects a single version of a module when multiple are requested.

mill-deps-tree.txt
gradle-compile-and-runtime-deps-tree.txt

@lihaoyi
Copy link
Member

lihaoyi commented Nov 20, 2024

CC @alexarchambault

@0xnm
Copy link
Contributor Author

0xnm commented Nov 20, 2024

@himanshumahajan138 This is not Compose or some other particular artifact issue, but a dependency resolution issue with the outcome different from the expected one (if we consider outcome of Gradle as a reference), so let's avoid adding more comments on "solution", because it doesn't lay in the build script, but either on the Mill side or Coursier side (are some resolution strategies used only for the particular artifact types? That would explain why resolution for aar artifacts didn't work).

What you are proposing simply doesn't work, because:

i used particular version which are not prone to errors

This is like throwing the dice randomly hoping for the good outcome. Dependency management system should resolve the case of the different versions for the same artifact.

override def mandatoryIvyDeps

This is rather a side note: you shouldn't normally override mandatoryIvyDeps in the build script, but rather use ivyDeps.

gradle use BOM for getting latest and non-dependency conflict versions of compose libs

Here it is not a conflict of Compose libs, but rather the conflict of transitive dependencies from the dependency tree. And no, BOM won't save you from the potential conflicts, because: a) it may not cover all transitive dependencies; b) user may explicitly specify the version of the artifact mentioned in BOM in their build script. For example, you can check what Compose BOM really contains and as you can see, it covers only Compose artifacts, but not their transitive dependencies, obviously.

Why Not Latest Versions ??? Coz, we have something inbuilt for Kotlin libs (i think foundation libs) which restricts the usage of latest libs becoz of dependency conflict

Nothing restricts anything, using latest versions is not a requirement and is not a solution. The only constraint is matching between Compose Compiler and Kotlin (because of compiler extensions), which is not pulled in the build script above.

The temporary correct workaround for such issues is using CoursierModule.mapDependencies with forcing a particular version for problematic dependencies.

@himanshumahajan138
Copy link
Contributor

@0xnm Sorry for Unusual "solution" Comments...

I was wrong and the way i was thinking was also wrong no worries Now i Got the Straight Look and clear mind for this issue, if possible and under my knowledge i will provide some good insights...

Apologies...

@alexarchambault
Copy link
Contributor

alexarchambault commented Nov 20, 2024

Adding a few more dependencies makes it work:

def repositoriesTask: Task[Seq[Repository]] = Task.Anon {
  super.repositoriesTask() :+ MavenRepository("https://maven.google.com/")
}

def ivyDeps: T[Agg[Dep]] = Agg(
  ivy"androidx.core:core-ktx:1.15.0",
  ivy"androidx.activity:activity-compose:1.9.3",
  ivy"androidx.compose.ui:ui:1.7.5",
  ivy"androidx.compose.material3:material3:1.3.1",

  ivy"androidx.lifecycle:lifecycle-runtime-ktx:2.6.2",
  ivy"androidx.lifecycle:lifecycle-process:2.8.3",
  ivy"androidx.collection:collection-ktx:1.4.4",
  ivy"androidx.lifecycle:lifecycle-common-java8:2.8.3"
)

(it does on the command-line for sure, let me know if ever it doesn't from Mill)

It's kind of tricky to explain in detail how I got these extra dependencies. I used the coursier command-line, cs (there are various ways to install it, and it can also be found for your OS and CPU in coursier release pages, such as this one).

It can be used like

$ cs resolve -r google androidx.core:core-ktx:1.15.0 androidx.activity:activity-compose:1.9.3

cs gives more details about conflicts when they happen, and the sub-trees it prints help make sense of what causes the conflicts (it helps finding out which dependencies introduce required versions, like [x.y.z], rather than minimum versions, like x.y.z, which helps finding which dependencies should be bumped to work around the conflicts).

@alexarchambault
Copy link
Contributor

We could maybe print those conflicts in Mill too. Making sense of them can be tricky sometimes though. Ideally, we should understand how Gradle deals with those, and maybe do the same or something similar in coursier.

@alexarchambault
Copy link
Contributor

Seems the detailed output in case of conflicts is printed in Mill too actually…

@alexarchambault
Copy link
Contributor

@0xnm In gradle-compile-and-runtime-deps-tree.txt that you attached, do you know where the lines with strictly come from?

+--- org.jetbrains.kotlin:kotlin-stdlib:{strictly 2.0.21} -> 2.0.21 (c)
+--- androidx.core:core-ktx:{strictly 1.15.0} -> 1.15.0 (c)
+--- androidx.activity:activity-compose:{strictly 1.9.3} -> 1.9.3 (c)
+--- androidx.compose.ui:ui:{strictly 1.7.5} -> 1.7.5 (c)
+--- androidx.compose.material3:material3:{strictly 1.3.1} -> 1.3.1 (c)
…66 such lines in total…

Could a BOM have been added somewhere?

@0xnm
Copy link
Contributor Author

0xnm commented Nov 21, 2024

I think I know what is going on here: probably it is because of the dependencyManagement in the POM files?

Because, for example, if we check POM file of collection-ktx, it declares the version of androidx.collection:collection in the dependencyManagement section. Does Coursier take this into account?

So the version can be brought as a transitive rule of the dependencyManagement rather than of dependencies.

I'm also attaching the result of dependencyInsight --configuration debugCompileClasspath --dependency collection Gradle task.

dependency-insight-androidx-collection.txt

Update: okay, it is not because of the dependencyManagement, but because of the module files, unfortunately. Let's take a look on the following sub-tree (I removed redundant stuff):

+--- androidx.activity:activity-compose:1.9.3
|    +--- androidx.compose.runtime:runtime:1.0.1 -> 1.7.5
|    |    \--- androidx.compose.runtime:runtime-android:1.7.5
|    |         +--- androidx.annotation:annotation-experimental:1.4.1 (*)
|    |         +--- androidx.collection:collection:1.4.4 (*)
|    |         +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.21 (*)

We can see, that androidx.compose.runtime:runtime-android:1.7.5 is referencing androidx.collection:collection:1.4.4, but if we check its POM file, it mentions only collection-jvm, but not collection module. But if we check module file, it will be the following block there:

"dependencies": [
  ...
  {
    "group": "androidx.collection",
    "module": "collection",
    "version": {
      "requires": "1.4.4"
    },
    "excludes": [
      {
        "group": "org.jetbrains.kotlin",
        "module": "kotlin-stdlib-common"
      },
      {
        "group": "org.jetbrains.kotlin",
        "module": "kotlin-test-common"
      },
      {
        "group": "org.jetbrains.kotlin",
        "module": "kotlin-test-annotations-common"
      }
    ]
  }
]

So I guess Gradle cuts the corner and relies on module file for the dependency resolution.

@alexarchambault
Copy link
Contributor

I think I know what is going on here: probably it is because of the dependencyManagement in the POM files?

The current coursier version that Mill uses (2.1.16) doesn't use versions in BOMs as overrides, it only uses them to fill empty versions in POMs. #3950 should make Mill switch to a newer coursier version (2.1.18), and have the BOMs be used as version overrides too.

That being said, while investigating these dependencies earlier, I basically got the same results with older (2.1.16) and newer (2.1.18) coursier versions, so I don't think that's the problem.

@0xnm
Copy link
Contributor Author

0xnm commented Nov 21, 2024

Yes, that is not because of the dependencyManagement, but probably because of the module files usage by Gradle as I posted in the update to my previous comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants