I have a Gradle build that includes a platform/BOM. There is also a project that has a bunch of sub projects under it. It is these projects that I want to include in the platform. To complicate things, the sub projects contains artifacts with classifiers. These artifacts should also be included in the platform. Not all sub projects have the same artifacts with classifiers and some have none.
The problem is that I want to avoid specifying these projects and their artifacts manually. The first step is easy. I can simply loop through the subprojects via project(":parent").subprojects.forEach{} and add them to the dependency constraints. The real problem comes when I need to add the artifacts with classifiers. Since the Java platform plugin do not support artifacts with classifiers, I need to add them manually with pom {withXML{ }}. This is problematic since in the platform project, I do not know which artifacts with classifiers that each sub project has.
To solve this I have created a precompiled convention plugin that all sub projects have. In this plugin, a task is created that produces a text file with a list of artifacts and their classifiers. This artifact is then added to a custom consumable configuration. The platform has the same configuration but it is resolvable instead. The platform can then add all sub projects as dependencies in this configuration. The next problem is that I cannot simply resolve this configuration straight away. I need to resolve it at execution time to avoid getting missing files exceptions.
To solve this, I moved the XML post processing to the POM generation task in a doLast. This resolves the configuration at execution time while still adding the necessary XML to the POM. My question is about whether this is a good solution? Can this be done with less steps and/or does it contain any bad practises? I would think that this problem is not super uncommon and that there is a best practice way of doing it. Maybe the best practice is not to automate it, but then I would have to manually specify the artifacts both in the sub project and the platform.
Here is the code:
Binary Plugin applied to sub projects (kotlin):
// Register task during plugin apply
project.tasks.register("generateMetadata")
// The task that generates the metadata declared later when the project has supplied the required info
project.tasks.named("generateMetadata") {
val outputFile = project.layout.buildDirectory.file("generated/metadata.txt")
val text = "The list of artifacts and their classifiers"
outputs.file(outputFile)
doLast {
val file = outputFile.get().asFile
file.parentFile.mkdirs()
file.writeText(text)
}
}
Precompiled plugin applied to sub projects (kotlin):
// Get metadata task
val metadataTask = tasks.named("generateMetadata")
// Custom metadata configuration
configurations.register("metadata"){
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, "metadata"))
}
outgoing.artifact(metadataTask){
classifier = "metadata"
extension = "txt"
builtBy(metadataTask)
}
}
Precompiled plugin applied to platform (kotlin):
// Get a list of the sub projects modules
val modules = mutableListOf<String>()
project(":modules").subprojects.forEach { subProject ->
modules.add(subProject.name)
}
// Custom metadata configuration
val metadataConfiguration = configurations.create("metadata"){
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, "metadata"))
}
}
// Add sub projects as dependencies with metadata configuration
dependencies {
modules.forEach { module ->
add(metadataConfiguration.name, project(":modules:${module}"))
}
}
// Data container for each artifact
class ClassifierArtifact(
val module: String,
val classifier: String,
)
tasks.withType<GenerateMavenPom>().configureEach {
doLast {
val classifierArtifacts = mutableListOf<ClassifierArtifact>()
metadataConfiguration.resolve().forEach { file ->
file.readLines().forEach { line ->
// lines are of the form group:name:version:classifier
val parts = line.split(":")
val id = parts[1]
val classifier = parts[3]
classifierArtifacts.add(ClassifierArtifact(id, classifier))
}
}
pom.withXml {
// Do XML post processing with classifierArtifacts list
}
}
}
dependencies {
constraints {
modules.forEach { module ->
api(project(":modules:${module}"))
}
}
}
The code has been cut out of context but I hope it shows enough to give some thoughts.