Skip to content

Instantly share code, notes, and snippets.

@aSemy
Last active September 20, 2025 06:39
Show Gist options
  • Save aSemy/8c3e248759dad5028623acc68e601a5d to your computer and use it in GitHub Desktop.
Save aSemy/8c3e248759dad5028623acc68e601a5d to your computer and use it in GitHub Desktop.
Gradle demo: add 'remote' variants to SoftwareComponents

Demonstrator for Support adding 'remote' variants to SoftwareComponents, so variants are published to separate coordinates and are linked via GMM relative URLs.

gradle/gradle#35063

Written using Gradle 9.1.0

Demo

Run gradle publishAllPublicationsToBuildDirRepository. Gradle will publish 3 separate, but linked, libraries to build/build-dir-maven.

image

build.gradle.kts

import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.publish.internal.component.DefaultAdhocSoftwareComponent
import org.gradle.kotlin.dsl.support.serviceOf

plugins {
  base
  `maven-publish`
}

group = "a.b.c.group"
version = "1.2.3"

publishing {
  repositories {
    maven(layout.buildDirectory.dir("build-dir-maven")) {
      name = "BuildDir"
    }
  }
}


val generateTextFile by tasks.registering {
  val output = temporaryDir.resolve("outgoing.txt")
  outputs.file(output)
  doLast {
    output.writeText("demo outgoing txt")
  }
}
val generateJsonFile by tasks.registering {
  val output = temporaryDir.resolve("outgoing.json")
  outputs.file(output)
  doLast {
    output.writeText("demo outgoing json")
  }
}


val baseOutgoingVariant = configurations.consumable("baseOutgoingVariant").get().apply {
  attributes {
    attribute(Usage.USAGE_ATTRIBUTE, objects.named("demo-lib"))
  }
}
val textOutgoingVariant = configurations.consumable("textOutgoingVariant").get().apply {
  attributes {
    attribute(Usage.USAGE_ATTRIBUTE, objects.named("demo-lib"))
    attribute(Category.CATEGORY_ATTRIBUTE, objects.named("text"))
  }
  outgoing {
    capability("a.b.c.group:demo-lib-text:1.2.3")
    artifact(generateTextFile) {
      type = "text"
    }
  }
}

val jsonOutgoingVariant = configurations.consumable("jsonOutgoingVariant").get().apply {
  attributes {
    attribute(Usage.USAGE_ATTRIBUTE, objects.named("demo-lib"))
    attribute(Category.CATEGORY_ATTRIBUTE, objects.named("json"))
  }
  outgoing {
    capability("a.b.c.group:demo-lib-json:1.2.3")
    artifact(generateJsonFile) {
      type = "json"
    }
  }
}


val softwareComponentFactory = serviceOf<SoftwareComponentFactory>()

val adhocComponent: SoftwareComponentWithVariants =
  SoftwareComponentWithVariants(softwareComponentFactory.adhoc("myAdhocComponent") as DefaultAdhocSoftwareComponent)


class SoftwareComponentWithVariants(
  private val baseComponent: DefaultAdhocSoftwareComponent
) : SoftwareComponentInternal, ComponentWithVariants, AdhocComponentWithVariants by baseComponent {

  val childVariants: MutableSet<SoftwareComponentVariant> = mutableSetOf()

  override fun getUsages(): Set<UsageContext> = baseComponent.usages

  override fun getName(): String = baseComponent.name

  /** @returns the 'children' published at different coords, linked via `relative-at` url */
  override fun getVariants(): Set<SoftwareComponent> =
    childVariants
}


class SoftwareComponentVariant(
  private val baseComponent: DefaultAdhocSoftwareComponent,
  private val gav: String,
) : SoftwareComponentInternal, PublishableComponent, AdhocComponentWithVariants by baseComponent {
  override fun getUsages(): Set<UsageContext> =
    baseComponent.usages

  override fun getName(): String = baseComponent.name

  private val coords = object : ModuleVersionIdentifier {
    private val _group = gav.substringBefore(':')
    private val _name = gav.substringAfter(':').substringBefore(':')
    private val _version = gav.substringAfterLast(':')

    override fun getVersion(): String = _version
    override fun getGroup(): String = _group
    override fun getName(): String = _name

    override fun getModule(): ModuleIdentifier {
      return object : ModuleIdentifier {
        override fun getGroup(): String = _group
        override fun getName(): String = _name
      }
    }
  }

  override fun getCoordinates(): ModuleVersionIdentifier = coords
}


components.add(adhocComponent)

adhocComponent.addVariantsFromConfiguration(baseOutgoingVariant) {
  mapToMavenScope("runtime")
}

val textComponentVariant: SoftwareComponentVariant =
  SoftwareComponentVariant(
    softwareComponentFactory.adhoc("textComponentVariant") as DefaultAdhocSoftwareComponent,
    "a.b.c.group:demo-lib-text:1.2.3"
  )
textComponentVariant.addVariantsFromConfiguration(textOutgoingVariant) {
  mapToMavenScope("runtime")
}
adhocComponent.childVariants.add(textComponentVariant)

val jsonComponentVariant: SoftwareComponentVariant =
  SoftwareComponentVariant(
    softwareComponentFactory.adhoc("jsonComponentVariant") as DefaultAdhocSoftwareComponent,
    "a.b.c.group:demo-lib-json:1.2.3"
  )
jsonComponentVariant.addVariantsFromConfiguration(jsonOutgoingVariant) {
  mapToMavenScope("runtime")
}
adhocComponent.childVariants.add(jsonComponentVariant)


publishing {
  publications {
    create<MavenPublication>("maven") {
      from(adhocComponent)
    }
    create<MavenPublication>("mavenText") {
      from(textComponentVariant)
      artifactId = "demo-lib-text"
    }
    create<MavenPublication>("mavenJson") {
      from(jsonComponentVariant)
      artifactId = "demo-lib-json"
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment