Project dependencies are cross-linked when project name's match, but project paths differ

When I have a project with the following structure, I get weird dependency trees.

├── app

│ ├── businesslogic

│ │ ├── api

│ │ └── impl

│ └── infrastructure

└── domainmodel

├── api

└── impl

The dependencies between the projects are as follows:

  • :app:businesslogic:impl depends on :app:businesslogic:api * :app:domainmodel:api depends on :app:businesslogic:api * :app:domainmodel:impl depends on :app:domainmodel:api * :app:domainmodel:impl depends on :app:businesslogic:impl

When I run the gradle command ‘gradle :app:infrastructure:domainmodel:impl:dependencies --configuration compile’, I receive the following output:


Project :app:infrastructure:domainmodel:impl


compile - Compile classpath for source set ‘main’.

±-- project :app:businesslogic:impl

|

— project :app:businesslogic:api

— project :app:infrastructure:domainmodel:api

— project :app:businesslogic:api

At this point, everything functions as expected. However, if I set the group to something like ‘com.mycompany’, I get strange dependency trees that look like this:


Project :app:infrastructure:domainmodel:impl


compile - Compile classpath for source set ‘main’.

±-- project :app:businesslogic:impl -> project :app:infrastructure:domainmodel:impl (*)

— project :app:infrastructure:domainmodel:api

— project :app:businesslogic:api -> project :app:infrastructure:domainmodel:api (*)

I realize that this is probably due to the fact that items are stored within the dependency store using the key of group and name, but this forces me to resolve the problem in one of two ways:

  1. Modify my directory structure to contain unique leaf nodes in my directory structure. Although this might be the “Gradle-way” it creates a problem when migrating an existing legacy Maven project. 1. Modify the way I create the projects with the unmanageable project declaration method (see below)
include 'app:businesslogic:api-A'
include 'app:businesslogic:impl-A'
include 'app:infrastructure:domainmodel:api-B'
include 'app:infrastructure:domainmodel:impl-B'
       project('app:businesslogic:api-A').projectDir = "$rootDir/app/businesslogic/api" as File
project('app:businesslogic:impl-A').projectDir = "$rootDir/app/businesslogic/impl" as File
project('app:infrastructure:domainmodel:api-B').projectDir = "$rootDir/app/domainmodel/api" as File
project('app:infrastructure:domainmodel:impl-B').projectDir = "$rootDir/app/domainmodel/impl" as File

What am I doing wrong?

according to the Gradle documentation, the definition of a ‘Project.name’ is:

The name of this project. The project’s name is not necessarily unique within a project hierarchy. You should use the ‘Project.getPath()’ method for a unique identifier for the project.

This is at odds with the way dependency resolution is operating for inner-project dependencies. Since a project’s name is not necessarily unique within a project hierarchy, the dependency resolution cannot use the project name or it must incorporate some other identifier to uniquely identify the project. Ironically, you MUST specify inner-project dependencies using the project path, which is unique.

Do you need to have the same group for your businesslogic and infrastructure subprojects? When you go to publish those artifacts, they’ll have the same group and artifact name and you’ll have similar problems.

This behavior seemed to change in Gradle 1.11+, so I think this is a bug. I created GRADLE-3202

Yes, they all have the same group and I agree, by default this would cause a problem when they are published.

However, I can assign unique artifact ids to each of the subprojects using the ‘Project.archivesBaseName’. This will resolve the problem you describe.

It was my hope to co-exist with the “Gradle-way” (e.g., project path closely correlated to directory). I have artifact ids that do not correlate to the project directory. By using this method, it can publish artifacts properly. However, referring to projects internally by the path is broken.

Is there a normal mapping between artifact id, project name and project path?

Can you set the project names to what you want the archivesBaseNames to be and then map them to the correct project path in settings.gradle?

What does your published pom/ivy look like (dependency-wise)?

Did I mention this is a legacy Maven project we are trying to convert to Gradle? The desire to keep the Maven build running while transitioning to Gradle, so restructuring the project directory structure is undesirable.

In my Maven project, the artifact-id and project name are equivalent. The project path is unrelated, but it is used to determine project hierarchy. Each subproject may uniquely identified by artifact id.

Although I can set the name and archivesBaseNames to be equivalent, I believe this is a BAD pattern:

include ‘app:businesslogic:api’

project(‘app:businesslogic:api’).projectDir = “$rootDir/app/businesslogic/api” as File

Why?

  1. The only way to create a new project is through the ‘include’ and it does not allow me to specify any additional information at the same time (e.g., project directory). Therefore, I must use the project path I previously supplied in order to look up the project and supply additional information.

  2. This syntax is extremely verbose. Gradle is, in a sense, punishing you for not keeping all sub-projects in a single directory where each project name is guaranteed to be unique (and shorter project paths). 1. When you have a hierarchy of long artifact ids, the project paths become unmanageable hard to use. I’ve simplified our use case for demonstration purpose, but some of our directory structures are five levels deep. This creates project paths that look something like this (very simplified version of what exists in my project):

:mc-myapp:mc-webservices:mc-webservice-2011-05-21:mc-marshalling:mc-marshalling-impl

vs.

:app:webservices:services:webservice-2011-05-20:marshalling:impl

The second case is much easier to specify and remember. It also follows the Gradle form, but requires me to specify the ‘archiveBaseName’. This is a tolerable penalty, but it Gradle is currently unable to support this approach because of GRADLE-3202.

Sorry, I wasn’t very clear. I’m not suggesting to rearrange any of the project files.

Your example in the original post shows projects with non-unique project names (api), but you’re saying that the subprojects all have unique artifact ids (via archivesBaseName). I was trying to suggest to use what you’re using for archivesBaseName as the project names.

The name you supply for include doesn’t have to match any kind of existing file structure. If you look at what we do in Gradle itself, we rearrange things and give the projects friendly names.

I think it’s perfectly fine to put the names you want to use and remap them to their physical locations. That’s why I was asking if there was a regular order to it because it would make it easier to script. Here’s sort of what I think you’re doing: https://github.com/big-guy/gradle-project-rename

Maybe it would be useful to have some sort of shortcut version of this (e.g., include name: “businesslogic-api”, path: “app/businesslogic/api”).

Does that help or am I still way off?

The problem I’m trying to resolve, and that led me to the issue reported here, is that I need to find a balance between the 'artifactId’s we need to create and the way in which Gradle wants to reference projects.

Maven provides a uniform way in which artifacts are referenced, regardless of source. Gradle makes a distinction in referencing internal and external artifacts. Whereas in Maven, I can reference a project by group and artifact id within a dependency declaration or even just the artifact id from the command line, in Gradle I must always specify the full project path.

In most Gradle projects I’ve observed, it appears that most multi-project builds have a very flat directory structure. In those cases, the Gradle-way works great because the project path equates to the leaf node. However, in a multi-project build with some level of hierarchy these project paths can become rather large. This can make artifact references error prone within dependencies and the command line.

As an example, the worst case of a project path in my project is the following (some letters have been changed for anonymity, but the character count is the same):

:fatengine-app-pom:mc-public-webservices-pom:mc-public-services-pom:mc-public-services-2010-11-20:xyz-mc-ws-public-marshalling-pom:xyz-mc-ws-public-marshalling-impl-pom:xyz-mc-ws-public-marshalling-impl-independent

I think you can see that this would be problematic to enter as a dependency and impossible to enter on the command line.

I’m not suggesting that Gradle change their mechanism of referencing internal projects, although it would be nice to have the option of referencing internal artifacts using the same attributes as an external artifact. I’m just looking for something that achieves a balance between what I have and the preferred Gradle-way of declaring multi-project dependencies.

What you’ve suggested is a mixture of ‘path’ and ‘artifactId’ where the parent projects would effectively be referenced by ‘directoryPath’ and only the leaf nodes would be referenced by ‘artifactId’. In that case, my example above becomes:

:app:public-webservices:services:2010-11-20:marshalling:impl:xyz-mc-public-marshalling-impl-independent

Although this is better, it is still a lot more tedious and error prone to enter than the group and artifact id I use in Mave.

I hope I’ve accurately described the balance I’m trying to achieve and I’ve gotten a bit off topic of the original issue, but I thought it was worth mentioning the struggles of converting a legacy Maven project where you cannot immediately modify the project structure.