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

Protoquill transitive internal dependency breaks sbt-assembly #480

Open
Nexus6 opened this issue Jul 31, 2024 · 2 comments
Open

Protoquill transitive internal dependency breaks sbt-assembly #480

Nexus6 opened this issue Jul 31, 2024 · 2 comments

Comments

@Nexus6
Copy link

Nexus6 commented Jul 31, 2024

Scala Version: 3.4.2
SBT Version: 1.10.1
Protoquill Version: 4.8.5
Module: quill-sql
Database: postgresql

Expected behavior

Expect to be able to use sbt-assembly to package a stand-alone executable jar file containing protoquill 4.8.5.

Actual behavior

sbt-assembly errors-out because protoquill 4.8.5 has a transitive dependency on quill-engine 4.8.4 and the two jars contain classes that are different, but have the same fully-qualified names. Two examples among many from the console output when I run "sbt assembly":

[error] Deduplicate found different file contents in the following:
[error] Jar name = quill-engine_3-4.8.4.jar, jar org = io.getquill, entry target = io/getquill/context/ProtoStreamContext.class
[error] Jar name = quill-sql_3-4.8.5.jar, jar org = io.getquill, entry target = io/getquill/context/ProtoStreamContext.class
[error] Deduplicate found different file contents in the following:
[error] Jar name = quill-engine_3-4.8.4.jar, jar org = io.getquill, entry target = io/getquill/context/ExecutionInfo.class
[error] Jar name = quill-sql_3-4.8.5.jar, jar org = io.getquill, entry target = io/getquill/context/ExecutionInfo.class

Apart from the "assembly" step, the project builds and runs ("sbt run") just fine. Running a trivial query that uses protoquill 4.8.5 from the project also works w/no problems. There seem to be no compile-time or runtime issues.

Steps to reproduce the behavior

Include protoquill in your build.sbt as:
"io.getquill" %% "quill-jdbc" % "4.8.5"
Add the sbt-assembly plugin to your plugins.sbt:

addSbtPlugin ("com.eed3si9n" % "sbt-assembly" % "2.2.0")

then run "sbt assembly"

Workaround

N/A
@getquill/maintainers

@mev42
Copy link

mev42 commented Sep 9, 2024

assembly / assemblyMergeStrategy := {
  case path if path.endsWith("io/getquill/context/ExecutionInfo$.class")         => MergeStrategy.last
  case path if path.endsWith("io/getquill/util/TraceConfig.class")               => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/AstSplicing.class")            => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionInfo.class")          => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionInfo.tasty")          => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ProtoStreamContext.class")     => MergeStrategy.last
  case path if path.endsWith("io/getquill/util/TraceConfig$.class")              => MergeStrategy.last
  case path if path.endsWith("io/getquill/util/TraceConfig.tasty")               => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType.tasty")          => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType$Dynamic$.class") => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType$Unknown$.class") => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ProtoStreamContext.tasty")     => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType$.class")         => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType.class")          => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/AstSplicing.tasty")            => MergeStrategy.last
  case path if path.endsWith("io/getquill/context/ExecutionType$Static$.class")  => MergeStrategy.last
  case x =>
    val oldStrategy = (assembly / assemblyMergeStrategy).value
    oldStrategy(x)
}

@Nexus6
Copy link
Author

Nexus6 commented Sep 10, 2024

This might be a temporary work-around, but seems awfully fragile, because

  1. It's dependent on the classpath ordering between the protoquill and quill-engine libraries. Even if MergeStrategy.last picks the correct class most of the time, the approach would fail if the classpath was reordered (intentionally or not) in such a way that the strategy grabs the quill-engine version of the class rather than the protoquill version.
  2. If any of the enumerated classes are renamed, or if classes are added to protoquill and/or quill-engine such that there are new conflicts, users will need to manually update their assemblyMergeStrategy.
  3. Mixing together classes from these two different builds of quill seems like asking for trouble. As the sbt-assembly docs put it, "Merge conflict of *.class files indicate pathological classpath". Wouldn't it be best to fix the root problem?

The first potential fixes that occur to me are:

  1. Move all protoquill classes to their own (new) package. Maybe something like net.protoquill.* ? This would of course require that users update their .sbt files and their import statements. Not sure how big a deal that would be. It would also require updates to the protoquill user docs to reflect the new package naming.
  2. Remove the transitive dependency by packaging quill-engine code into the protoquill library and exclude classes from the former, when there's a conflict, as part of the protoquill build/release process.

I could be wrong but it seems to me that protoquill is probably dependent on classpath ordering at the moment just to work at all. If so this seems like something that needs to be fixed even if 'playing nice' with sbt-assembly isn't deemed a high priority.

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

2 participants