gradlex-org / java-module-dependencies

A Gradle plugin to use dependencies from 'module-info.java' files.
Apache License 2.0
48 stars 9 forks source link

Support projects with same name but different project paths #108

Closed tg-freigmbh closed 3 days ago

tg-freigmbh commented 5 months ago

I get this stacktrace: {code} Caused by: java.lang.IllegalStateException: Duplicate key Localization (attempted merging values UMS and UMS.Utilities) at org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension.lambda$create$14(JavaModuleDependenciesExtension.java:216) at org.gradle.api.internal.provider.DefaultProvider.calculateOwnValue(DefaultProvider.java:72) at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:115) at org.gradle.api.internal.provider.TransformBackedProvider.calculateOwnValue(TransformBackedProvider.java:81) at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:115) at org.gradle.api.internal.provider.Collectors$ElementFromProvider.collectEntries(Collectors.java:100) at org.gradle.api.internal.provider.Collectors$TypedCollector.collectEntries(Collectors.java:334) at org.gradle.api.internal.provider.Collectors$TypedCollector.collectInto(Collectors.java:329) at org.gradle.api.internal.collections.AbstractIterationOrderRetainingElementSource$Element.realize(AbstractIterationOrderRetainingElementSource.java:345) at org.gradle.api.internal.collections.AbstractIterationOrderRetainingElementSource.realizePending(AbstractIterationOrderRetainingElementSource.java:142) at org.gradle.api.internal.DefaultDomainObjectCollection.addEagerAction(DefaultDomainObjectCollection.java:224) at org.gradle.api.internal.DefaultDomainObjectCollection.all(DefaultDomainObjectCollection.java:142) at org.gradle.api.internal.CompositeDomainObjectSet.addCollection(CompositeDomainObjectSet.java:114) at org.gradle.api.internal.CompositeDomainObjectSet.create(CompositeDomainObjectSet.java:58) at org.gradle.api.internal.collections.DefaultDomainObjectCollectionFactory.newDomainObjectSet(DefaultDomainObjectCollectionFactory.java:112) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.initAllDependencies(DefaultConfiguration.java:961) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.getAllDependencies(DefaultConfiguration.java:946) at org.gradle.api.internal.artifacts.configurations.DefaultUnlockedConfiguration_Decorated.getAllDependencies(Unknown Source) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.initAllDependencies(DefaultConfiguration.java:963) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.getAllDependencies(DefaultConfiguration.java:946) at org.gradle.api.internal.artifacts.configurations.DefaultUnlockedConfiguration_Decorated.getAllDependencies(Unknown Source) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.hasDependencies(DefaultConfiguration.java:954) at org.gradle.api.internal.artifacts.ivyservice.ShortCircuitEmptyConfigurationResolver.resolveGraph(ShortCircuitEmptyConfigurationResolver.java:85) at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingConfigurationResolver.resolveGraph(ErrorHandlingConfigurationResolver.java:77) ... 177 more {code} My Project names are not unique, basically there is a Project :Server:Localization :Client:Localization, :Installer:Localization, ...

Soo I assume its basically not possible to use this plugin under these circumstances?

jjohannes commented 5 months ago

Yes your assumption is correct. Right now, the plugin does not support such setups. The project name of each subproject needs to be unique and match the (last part) of the Module Name in the module-info.java file.

The setup of using hierarchies like you do is also slightly discouraged in Gradle as I perceive it. There are more issues you could run in with other Gradle functionality (like this long standing issue/discussion).

My recommendation for projects is to always use uniques names for the logical project name, while keeping the physical location as it is. In your example this would mean defining projects like this:

include(":server-localization")
project(":server-localization").projectDir = file("Server/Localization")
include(":client-localization")
project(":client-localization").projectDir = file("Client/Localization")
include(":installer-localization")
project(":installer-localization").projectDir = file("Installer/Localization")

And the module-info.java files would have names like:

module com.mycompany.myproduct.server.localization {
}
module com.mycompany.myproduct.client.localization {
}
module com.mycompany.myproduct.installer.localization {
}

If for some reason you need/want to keep the setup with the project hierarchies and duplicated names, I would be open to extend the plugin to use the "subproject path" instead of just the "subproject name" in the mapping of "Module Name" to "Subproject". If that can be added without breaking existing functionality.

tg-freigmbh commented 5 months ago

Ok. I am currently migrating existing sources to gradle from the bottom up. So I somewhat have to keep existing structures and modulenames (even if they are stupid/nonconsistent), because consumers that have not been migrated yet expect them to be that way, I can give your recommendation a try. Another suggestion/ticket, maybe for another ticket I can manually specify moduleNamToGa, which is really helpful. Wouldnt it be possible to manually specify moduleNameToProjectPaths or something similar?

jjohannes commented 5 months ago

You can already use moduleNamToGa for local projects. For that it is required to add this to settings.gradle:

includeBuild(".")

Then you can also address local projects by group:name notation. Then, If all you Localization projects have different groups, you can configure things like this:

javaModuleDependencies {
    moduleNameToGA.put("com.my.server.loc", "my.server:Localization") // 'my.server' defined by `group = "my.server"`
}

This would already work, if you won't get the exception before.

If you want, you can try it by building this plugin from source. (clones this repo; and then add pluginManagement { includeBuild("path-to-local-clone") } to your settings)

And adjust this line here such that it skips duplicate instead of failing: https://github.com/gradlex-org/java-module-dependencies/blob/main/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java#L217

Maybe that can be a "good enough" solution for you. I am happy to accept a PR with such a change.

tg-freigmbh commented 5 months ago

Thanks for the merge, I think this issue can be closed. I was not aware of the project isolation feature, but you are right, in that case another solution is needed.

jjohannes commented 3 weeks ago

Need to figure out if implementing #136 would be good enough to solve this as well.

jjohannes commented 3 days ago

@tg-freigmbh this is now supported when using the settings plugin on top of Gradle's normal include("...") (since 1.8). For example, you can do something like this:

include(":app1:feature1:data")
include(":app1:feature2:data")

rootProject.children.forEach { appContainer ->
    appContainer.children.forEach { featureContainer ->
        featureContainer.children.forEach { module ->
            javaModules.module(module) {
               group = "org.example" // this configuration block is optional
            }
        }
    }
}

Please let me know, if this is not sufficiently resolving the issue.

tg-freigmbh commented 3 days ago

looks great so far, I will give feedback when I have some time refactoring the build