Repository navigation
Allow duplicate project names if they have distinct groups #281
Description
Activity
I think it's possible for nmcp to create a more refined check (check the resolved components from ResolvedComponentResult - if they are project dependencies then check to see if the expected project matches the actual).
But really it should be fixed in Gradle, so I made a PR: gradle/gradle#39241
I can also make a PR for the improved check in nmcp?
Thanks for raising this and offering to help. This is very kind but given that the workaround is pretty simple, I'd rather not add too much code for this.
Overall, I feel like the false positives raise awareness about the issue without being blocking so I'm enclined to keep them.
I've tweaked the error message a little bit there. Let me know if that works for you.
Thanks for raising this and offering to help. This is very kind but given that the workaround is pretty simple, I'd rather not add too much code for this.
Agree! The updated check is mostly finished, but I realised it was overkill for such a small check in nmcp.
For future reference, here's the check. Maybe it's useful elsewhere.
import org.gradle.api.artifacts.component.ProjectComponentSelector import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.artifacts.result.UnresolvedDependencyResult import org.gradle.api.attributes.Usage /** * Fails if one of the aggregated projects was swallowed by dependency resolution. * * Gradle identifies the projects taking part in dependency resolution by their `group:name` coordinates. * If two projects share the same coordinates, Gradle cannot tell them apart: * - if both of them expose the `nmcp` variant, one is silently replaced by the other and its artifacts never make * it to the aggregation. * - else the dependency is unresolved and Gradle reports a confusing "no matching variant" error about a project * that was never requested. This happens when the aggregating project itself takes part in the conflict. * * [root] is the component of the aggregating project, i.e. the project applying `com.gradleup.nmcp.aggregation`. It * is not necessarily the root project of the build. * * This is a best effort check: it may miss conflicts. Set `allowDuplicateProjectNames` to `true` to disable it. * * See https://github.com/gradle/gradle/issues/36167 for more details. */ internal fun checkProjectCoordConflict( root: ResolvedComponentResult, allowDuplicateProjectNames: Boolean, lenient: Boolean, ) { if (allowDuplicateProjectNames) { return } root.dependencies.forEach { dependencyResult -> val requested = dependencyResult.requested as? ProjectComponentSelector ?: return@forEach when (dependencyResult) { is ResolvedDependencyResult -> { val selected = dependencyResult.selected if (!requested.matchesStrictly(selected.id)) { duplicateProjectsError( "project '${requested.projectPath}' was replaced by ${selected.id.displayName} during dependency resolution because they have the same group and name ('${selected.moduleVersion?.module}'). The artifacts of '${requested.projectPath}' are missing from the aggregation.", ) } } is UnresolvedDependencyResult -> { if (lenient) { // If lenient, unresolved projects are expected. They are projects without `com.gradleup.nmcp`. return@forEach } // Gradle reports the missing `nmcp` variant on the component that actually took part in the // resolution. // - If it is [root], the requested project was collapsed into [root] by conflict // resolution and never resolved at all. // - If it is the requested project itself, that project simply does not apply `com.gradleup.nmcp`, // so let Gradle report it. if (!dependencyResult.missesTheNmcpVariantOn(root.id.displayName)) { return@forEach } val rootModule = root.moduleVersion ?: return@forEach duplicateProjectsError( "project '${requested.projectPath}' could not be resolved because it has the same group and name as ${root.id.displayName} ('${rootModule.module}').", ) } } } } /** * Whether variant selection failed on [componentDisplayName] for lack of an `nmcp` variant. * * The failure itself is always about the requested project ("Could not resolve project ':foo'.") and its cause names * the component that variant selection actually ran on ("No matching variant of project ':foo' was found."). * Those two differ when the requested project was collapsed into another component by conflict resolution. * * This matches Gradle error messages and may stop matching in a future Gradle version. If it does, the aggregation * fails with the Gradle error instead of ours. */ private fun UnresolvedDependencyResult.missesTheNmcpVariantOn(componentDisplayName: String): Boolean { val cause = failure.cause?.message.orEmpty() return cause.contains("No matching variant of $componentDisplayName was found") && cause.contains("attribute '${Usage.USAGE_ATTRIBUTE.name}' with value '$usageValue'") } private fun duplicateProjectsError(reason: String): Nothing { error( """ |Nmcp: $reason |You can usually resolve this error by renaming one of the projects (including possibly your root project) or by giving them distinct `group` values. |Or you can disable this check by calling `allowDuplicateProjectNames.set(true)`. |See https://github.com/gradle/gradle/issues/36167 for more details. """.trimMargin(), ) }
Reacted by Martin BonninThanks again for the help! I'm going to close this one for now. Hopefully this can be handled on the Gradle side in the future. If this becomes too much of an issue, we can always revisit.
Reacted by Adam
Expected and Results
nmcp throws an error if projects have the same name, for example if the root project is called
:fooand there's a subproject:bar:foo.allowDuplicateProjectNames.set(true)disables the check - so I'm not blocked.However, in my case the error is not valid. The two subprojects have distinct
project.groupvalues. A distinct group allows Gradle to differentiate the projects correctly.Can the warning be updated to check if the name and group of projects clash?
Related environment and versions
No response
Reproduction steps
No response
Anything else?
'${'$'}{it.name}'-- oops!nmcp/nmcp/src/main/kotlin/nmcp/internal/DefaultNmcpAggregationExtension.kt
Line 70 in 68fbd33