-
-
Notifications
You must be signed in to change notification settings - Fork 347
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
Fix conflicting dependencies between upstream JavaModule
s
#2735
Fix conflicting dependencies between upstream JavaModule
s
#2735
Conversation
@@ -424,8 +427,7 @@ trait JavaModule | |||
* necessary to run this module's code after compilation | |||
*/ | |||
def runClasspath: T[Seq[PathRef]] = T { | |||
localClasspath() ++ | |||
upstreamAssemblyClasspath() | |||
resolvedRunIvyDeps().toSeq ++ transitiveLocalClasspath() ++ localClasspath() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We remove upstreamAssemblyClasspath
so we can remove the dependency on unmanagedClasspath
, since it now also comes from localClasspath
, and we want to avoid duplicate classpath entries
*/ | ||
def localClasspath: T[Seq[PathRef]] = T { | ||
compileResources() ++ resources() ++ Agg(compile().classes) | ||
localCompileClasspath().toSeq ++ resources() ++ Agg(compile().classes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change adds unmanagedClasspath
to localClasspath
. This is probably the right thing to do, since it means it'll probably be aggregated from upstream modules into upstreamAssemblyClasspath
, where it wouldn't before
5484266
to
4d03f0b
Compare
// We aggregate upstream module `compileIvyDeps` during compilation | ||
"com/lihaoyi/sourcecode_2.13/0.2.2/sourcecode_2.13-0.2.2.jar", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is correct: should compileIvyDeps
be transitive at all? But it is the current behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compileIvyDeps
should not be transitive. They only are for local compilation. Downstream consumers need to provide them themselves if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that I was mistaken, and this was a normal ivyDeps
but in a compileModuleDeps
.
@lefou it seems you left a comment in here stating that this is necessary. Do you remember the reasoning?
I'm finding that without propagating ivyDeps
from the compileModuleDeps
, a whole bunch of Mill's build stops compiling because e.g.
A
ends up compiling against moduleB
with ivy dependencyC
on it's classpath- But if
B
has a method that returns a type fromC
, thenA
will cause the compiler to crash since it needs to haveC
on the classpath to look up and compile against
It seems to me that this should apply equally for ivyDeps
and compileIvyDeps
. Currently, a module's compileClasspath
only picks up ivyDeps
from it's compileModuleDeps
, but does not pick up it's compileIvyDeps
. This has likely not been a problem so far only because compileIvyDeps
is so uncommonly used. This odd edge case in the behavior is evident in the following part of the test case
Given that ivyDeps
and compileIvyDeps
have the same problem, should they be inherited by compileClasspath
in the same way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha, I asked myself the same question, when I tried to fix this issue. Unfortunately I don't remember exactly. The timing for me to deep dive into this issue is a bit unlucky. I hope I can later review and think about it. (I also pushed my WIP from last night, just to see the CI results.)
I think we definitely want the handle compileIvyDeps
and compileModuleDeps
equally, as the latter is converted to the former once published, and Mill should behave the same, disregarding the compile-time-dependency comes from the local build or from a coursier dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, this isn't super urgent, so I'll leave it open until you have a chance to think about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think feel like the best thing to do here is to have all compile dependencies and run dependencies be transitive, but limited to their respective classpaths.
Otherwise, there'll be no end to problems where ModuleA compiling against an upstream ModuleB fails due to ModuleB compiling against ModuleC and exposing it in a type signature. Similarly, for run dependencies, it makes sense that an application that needs e.g. log4j at runtime will continue to need log4j at runtime if used as a library for another application.
It's possible to work around these issues in user land, but it would basically always come down to cherry picking arbitrary dependencies from your upstream modules until things work, or manually traversing the module graph and collecting everything. The former is fragile and the latter is something that it feels like we should automate.
This would make compile{Ivy,Module}Deps
diverge from how Maven treats provided
scopes (those are non-transitive), but it seems like a better way of doing things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are various out-side givens to incorporate. E.g. we consume dependencies via coursier, so we can't change how transitivity is handled by it, hence, we are forced to adapt our inter-module dependencies handling likewise.
Also, Maven-like provided
dependencies are useful for compile-time-only APIs, like macros (like acyclic
?), which should not leak transitively. Or to ensure to build against some older baseline APIs. You don't want to force those old versions transitively, but you want to force your local compilation to use that old version to ensure, you don't accidentally use newer stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. We can't change the maven pom scopes, and those have no way to express "transitive compile time dependency". That means that no matter what we do with moduleDeps
, there's no way to have a transitive compile time dependency published to maven central
I guess in that case, it sounds like we follow the current status quo, where the compileClasspath
picks up the transitive *Deps
of compile*Deps
, but not the transitive compile*Deps
.
I'm going to merge this as is for now: with That should mostly match how |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks reasonable to me. It's a bit hard to review in the browser, due to the various other (unrelated) refactings.
I found one potential discrepancy between transitiveCompileClasspath
and bspTransitiveCompileClasspath
. To former now used localCompileClasspath
whereas the latter still uses bspCompileClasspath
which should be in sync with compileClasspath
.
But if I look closer, bspCompileClasspath
is also using localCompileClasspath
. It could be right, but hard to tell in the browser.
Also, this PR changes the order of dependencies and puts all elements from ivy
sources before others. This is a behavioral change. Was this intentional?
Somewhat. The status quo was broken, so of course we'd need to change things, so I while changing things I thought we might as well put things in a consistent order. With this PR, the order is always ivy deps, then upstream modules, then the current module. IIRC the previous orderings weren't as consistent between the various |
Hm, they weren't in order since Mill 0.11.0-M8. Before, we always had local dependencies first. Tbh, I don't care so much, but I have a vague memory of some issue in a customers project, when we changed the order in the generated IDEA projects. We then reverted/reworked the generator and made it consistent with Mill CLI. I have no pointers right now. (Could be something with multiple |
Classpath ordering is definitely a risk of breakage, it happens when a user has multiple different files at the same path on the classpath in different jars or classfile folders. But I don't think that's something we should guarantee too strongly. Even listing things on the filesystem can vary in order, so if a user deploys jars in a folder and does |
Fixes #2732
We introduce a new
localCompileClasspath
, which differs fromcompileClasspath
in that it leaves out thetransitiveCompileClasspath
from upstream modules andresolvedIvyDeps
from third party dependencies.Also tried to do some cleanup of
JavaModule
, refactoring out atransitiveModuleCompileModuleDeps
helper to DRY things up, re-arranging things so all thefooModuleDeps
helpers are all together.Tested via a new unit test
mill.scalalib.HelloWorldTests.multiModuleClasspaths
, which fails on master and passes on this PR. This test case asserts the overall "shape" of the classpaths, which should help rule out an entire class of mis-configuration w.r.t. how the classpaths are wired up