Is there any way to control the order of module de...
# intellij
j
Is there any way to control the order of module dependencies imported from gradle? I need the highlighted dependency to be prioritized before the one below it, but when imported after a gradle sync, it's at the bottom. This is a KMP project, where the lower priority dependency is a
compileOnly
dependency in an intermediate android + jvm source set. The android dependency is what gets compiled for the android target. With the default dependency priority order, the IDE shows a false positive error for a conflicting API between the two libraries.
Moving the dependency up in priority fixes the error in the IDE. But it's reverted after the next gradle sync.
e
couchbase's fault for not publishing with gradle module metadata, but you can fix it locally
(in
settings.gradle.kts
, change
dependencyResolutionManagement
to
dependencies
if you're putting it in
build.gradle.kts
) this should cause Gradle to redirect the dependency java→android or android→java depending on the target, and IntelliJ will use what Gradle ended up resolving
j
Thank you! This fixes it nicely! Now to learn more about gradle module metadata and how this works. And maybe help Couchbase publish the metadata themselves.
I've been looking into getting Couchbase to publish this gradle module metadata in their builds. I've figured out how to get the metadata in the publication, but the problem is the two variants are published from two separate modules, so the variants metadata is not aware of and doesn't include the other module variant. Is it possible to manually define a variant to publish in the metadata?
e
hmm. https://docs.gradle.org/current/userguide/publishing_customization.html is how you'd approach it in general but I'm not sure if it can be manipulated into adding dependencies that don't exist (yet, as far as the current build is concerned)
ah, is this the source? https://github.com/couchbase/couchbase-lite-java-ce that's some atypical Gradle and source layout they have; it seems to me that this could be more easily resolved if they were modules within one Gradle build instead of independent Gradle builds
j
Yes, that's the source, and yeah the gradle layout is a bit complex. It handles similar code sharing between platforms and editions that I'm needing to handle in my KMP bindings for the library, except it's mostly Java code (except for the
android-ktx
Kotlin extensions). There's another closed-source
ee
module, which I'm assuming looks similar to this
ce
module (I don't work for Couchbase), with the same
java
,
android
, and
android-ktx
modules with the additional enterprise APIs.
I believe I may have got this working, at least for the
java
module, adding the
android
variant with a dependency on the
android
module's artifact. If I understand correctly how your injected workaround code works, it sees another variant published in the Gradle Module Metadata with the same attribute with a different platform value, so it then excludes this variant's dependencies, which is the other platform's artifact?
First I had to make these changes to get the publication to actually publish the Gradle Module Metadata.
Then I added this code to the
:ce:java:ce_java
build.gradle:
Copy code
def androidApiConfig = configurations.create("androidApi").tap {
    canBeResolved = false
    canBeConsumed = true
    attributes {
        attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment.class, TargetJvmEnvironment.ANDROID))
        attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_API))
    }
}

dependencies {
    androidApi("com.couchbase.lite:couchbase-lite-android:$version")
}

def androidRuntimeConfig = configurations.create("androidRuntime").tap {
    extendsFrom(androidApiConfig)
    canBeResolved = false
    canBeConsumed = true
    attributes {
        attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment.class, TargetJvmEnvironment.ANDROID))
        attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME))
    }
}

(components.java as AdhocComponentWithVariants).with {
    addVariantsFromConfiguration(androidApiConfig) {
        mapToMavenScope("compile")
    }
    addVariantsFromConfiguration(androidRuntimeConfig) {
        mapToMavenScope("runtime")
    }
}
This adds these additional variants to the published .module file:
Copy code
{
  "name": "androidApi",
  "attributes": {
    "org.gradle.jvm.environment": "android",
    "org.gradle.usage": "java-api",
    "org.jetbrains.kotlin.platform.type": "androidJvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-android",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
},
{
  "name": "androidRuntime",
  "attributes": {
    "org.gradle.jvm.environment": "android",
    "org.gradle.usage": "java-runtime",
    "org.jetbrains.kotlin.platform.type": "androidJvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-android",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
}
Note I'm adding both
org.gradle.jvm.environment
and
org.jetbrains.kotlin.platform.type
attributes. The
java
module adds both, although the
android
module is only adding
org.jetbrains.kotlin.platform.type
to the
-published
configurations that get published. The equivalent configurations without the
-published
suffix also includes the other attributes, as well as a few others that are removed from the published version for some reason. I'm assuming this is something the Android gradle plugin is doing, and I'm not sure how to change this behavior. But it does seem Gradle can disambiguate using either of these attributes, as I tested the workaround with the other attribute as well.
So assuming this works for the
java
module (I didn't get a build error or anything about the dependency not existing, even after deleting the SNAPSHOT build from local maven). I just need to figure out how to do the same thing for the
android
module. I ran into this error trying similar code, but with the
components.release
component:
A problem occurred configuring project 'ceandroid:ce_android'.
> No signature of method: org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinTarget$buildAdhocComponentsFromKotlinVariants$1$2.addVariantsFromConfiguration() is applicable for argument types: (org.gradle.api.internal.artifacts.configurations.DefaultConfiguration_Decorated...) values: [configuration 'ceandroidce androidjvmApi', build_826wsussfhbm4sf80h1ug05zw$_run_closure18$_closure74@1790be3f]
So it seems
components.release
is not an
AdhocComponentWithVariants
with the ability to call
addVariantsFromConfiguration()
. So far I haven't been able to find an example of how to do this in an Android module.
I finally found this reference to someone calling this method in an Android project from back in 2019, which got me thinking to try some older AGP versions. I went from 7.4.2 all the way back to 7.0.4 until it finally worked! I also tried with 8.0-8.2 and it's still broken in those versions too. Seems as if the expectation is for components to implement the
AdhocComponentWithVariants
interface, but AGP is no longer doing this.
Looks like the component is this anonymous object, where the required reference is the
adhocVariant
that it wraps.
If I access the
AdhocComponentWithVariants
captured variable reference via reflection, this works to publish the Java variant for the Android module too.
Copy code
def adhocField = components.release.class.getDeclaredFields()
        .find { it.getType() == AdhocComponentWithVariants }
adhocField.setAccessible(true)
AdhocComponentWithVariants adhocComponent = adhocField.get(components.release)

adhocComponent.addVariantsFromConfiguration(...) {
    ...
It adds these additional variants to the Android published .module file:
Copy code
{
  "name": "androidApi",
  "attributes": {
    "org.gradle.jvm.environment": "android",
    "org.gradle.usage": "java-api",
    "org.jetbrains.kotlin.platform.type": "androidJvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-android",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
},
{
  "name": "androidRuntime",
  "attributes": {
    "org.gradle.jvm.environment": "android",
    "org.gradle.usage": "java-runtime",
    "org.jetbrains.kotlin.platform.type": "androidJvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-android",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
}
Would you be able to confirm if this is the correct GMM, essentially the equivalent of what the workaround code is doing in my project now?
Comparing the publications before and after these changes, it looks like the only additional side effect, besides the GMM and additional variants defined there, is that the .pom file now includes the other variant's artifact under its dependencies. I suppose this makes sense because of the way it's defined as a dependency of a variant, but really it's just the variant itself, which has a different artifact name. I would think to prevent this change from affecting consumers that use the .pom instead of .module file, that I should filter the dependency out of the .pom XML. Maybe this is beginning to be too much of a hack though.
I went ahead and filtered the other variant dependency out of the .pom. They were already doing some similar .pom dependency filtering in the XML, so I suppose it's acceptable. I also tested a local publication in my project and the .module accomplishes the same effect as the workaround code, excluding the other variant dependency. Thank you very much for your help on this @ephemient.
Another way this could be improved would be to use
available-at
to point to the location of the published artifact's files, rather than making the artifact a dependency of the variant. This would keep it out of the .pom too and be more correct. So instead of:
Copy code
{
  "name": "javaApi",
  "attributes": {
    "org.gradle.jvm.environment": "standard-jvm",
    "org.gradle.usage": "java-api",
    "org.jetbrains.kotlin.platform.type": "jvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-java",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
},
{
  "name": "javaRuntime",
  "attributes": {
    "org.gradle.jvm.environment": "standard-jvm",
    "org.gradle.usage": "java-runtime",
    "org.jetbrains.kotlin.platform.type": "jvm"
  },
  "dependencies": [
    {
      "group": "com.couchbase.lite",
      "module": "couchbase-lite-java",
      "version": {
        "requires": "3.1.0-SNAPSHOT"
      }
    }
  ]
}
it'd be:
Copy code
{
  "name": "javaApi",
  "attributes": {
    "org.gradle.jvm.environment": "standard-jvm",
    "org.gradle.usage": "java-api",
    "org.jetbrains.kotlin.platform.type": "jvm"
  },
  "available-at": {
    "url": "../../couchbase-lite-java/3.1.0-SNAPSHOT/couchbase-lite-java-3.1.0-SNAPSHOT.module",
    "group": "com.couchbase.lite",
    "module": "couchbase-lite-java",
    "version": "3.1.0-SNAPSHOT"
  }
},
{
  "name": "javaRuntime",
  "attributes": {
    "org.gradle.jvm.environment": "standard-jvm",
    "org.gradle.usage": "java-runtime",
    "org.jetbrains.kotlin.platform.type": "jvm"
  },
  "available-at": {
    "url": "../../couchbase-lite-java/3.1.0-SNAPSHOT/couchbase-lite-java-3.1.0-SNAPSHOT.module",
    "group": "com.couchbase.lite",
    "module": "couchbase-lite-java",
    "version": "3.1.0-SNAPSHOT"
  }
}
I'm not sure if there's an API for this though.