Background: in process of upgrading to Gradle 6.1.1 + Eclipse 2019-12, heavily applied configuration avoidance practices.
Eclipse has a concept of project-to-project dependencies which are separate from classpath dependencies. For Java projects those inter-project dependencies can be seen:
Project “Properties” -> “Java Build Path” section -> “Projects” tab, showing “Required projects on the build path”
In the project’s .project file, inside …
Eclipse (used to) use these to control the project build order. This is especially important if parallel building is enabled in Eclipse. Without this we witnessed intermittent (frequent) build errors requiring repeated refresh + build cycles to complete successfully.
Explicitly listing project dependencies using Eclipse plugin doesn’t seem to help (it used to work before). Is this related to configuration avoidance? When Buildship imports projects it isn’t really executing tasks, so it may not trigger all configuration? How does this work? Is there a preferred way to set inter-project dependencies?
A bit of a background as there are two issues working somewhat together here:
Gradle offers us fine grain control over “dependency configurations” (and dependencies). These can depend on artifacts, local file system, other projects and (other) project configurations (and do some more). Some come out-of-box but we can create our own.
One of the out-of-box configurations is (called) “default”. In Java world it brings together what the project’s code needs with that code itself, packaged in a jar.
Normally, when one project’s code expresses a “project” dependency it isn’t actually a project dependency at all but a dependency on that project’s default configuration. Difference is small but significant and important. If you think about it, if one project depends on another it means multiple similar but distinct things not captured by simplified dependency on project’s default configuration. Imagine project A depending on project B. At high level this usually means the following:
A’s implementation depends on B’s code (without transitive dependencies in the strictest sense)
A’s runtime (runtimeClasspath) additionally depends on B’s transitive implementation dependencies + all runtime dependencies (transitively as well)
A’s testImplementationpossibly depends on B’s test code (without transitive dependencies in the strictest sense)
A’s test runtime (testRuntimeClasspath) possibly additionally depends on B’s transitive test implementation dependencies + all test runtime dependencies (transitively as well)
Depending on default requires/depends on building jars which takes some time and space that isn’t always needed - say to run unit tests, as these can be run directly from unpackaged output directories.
Now:
To get clean dependencies as in (3) and avoid dependency pollution that would stem from either explicit or implied dependencies on default configurations and to squeeze a little bit more build performance (4), we do not use default configurations at all. We explicitly use clean configurations and define some of our own (to include actual output directories as opposed to jars, for example).
The issue:
Inter-project dependencies end up missing in Eclipse (see Eclipse inter-project dependencies missing) probably because Buildship only looks at dependencies on default configuration that we specifically omit.
Resolutions?
In the short term we will try to recognize/detect Buildship in/from our Gradle scripts and declare “default” default configuration dependencies. We’ve confirmed that this addresses the inter-project dependencies issue. We are hoping that there is a way to detect Buildship indirectly, such as by setting some system property for Eclipse (as we don’t know of a direct way). Will report.
In the long term:
Gradle should evolve beyond the insufficient way of declaring project dependencies to simply be dependencies on default configuration.
Buildship should also consider non-default configuration dependencies when determining Eclipse inter-project dependencies.