Robert Elliot
02/11/2025, 4:01 PM:app, which has the spring boot plugin applied, so it generates a runnable jar artifact from tasks.bootJar into layout.buildDirectory.dir("libs") - in practice, app/build/libs/app-boot.jar. It also has the application plugin so it generates app/build/libs/app-plain.jar (which does not contain all the other deps).
I would like the parent project to copy app-boot.jar into is build dir - layout.buildDirectory.dir("artifacts"), in practice build/artifacts/app-boot.jar.
Non-working attempt in thread...Robert Elliot
02/11/2025, 4:02 PMval desiredArtifacts: Configuration by configurations.creating {
isTransitive = false
}
dependencies {
desiredArtifacts(<http://projects.app|projects.app>)
}
tasks {
val copyArtifacts by registering(Copy::class) {
from(desiredArtifacts)
into(layout.buildDirectory.dir("artifacts"))
dependsOn(":app:assemble")
}
assemble {
dependsOn(copyArtifacts)
}
}Robert Elliot
02/11/2025, 4:04 PMartifacts {
add("archives", tasks.bootJar)
}Robert Elliot
02/11/2025, 4:04 PMjar task, not the one built by the bootJar taskVampire
02/11/2025, 4:05 PMVampire
02/11/2025, 4:06 PMgradlew :app:outgoingVariants whether there maybe is already a variant declared for the boot jar that you could just request.Vampire
02/11/2025, 4:07 PMRobert Elliot
02/11/2025, 4:07 PMbootArchivesVampire
02/11/2025, 4:08 PMdesiredArtifacts configuration, or on the concrete dependency.Robert Elliot
02/11/2025, 4:22 PM--------------------------------------------------
Variant bootArchives
--------------------------------------------------
Configuration for Spring Boot archive artifacts.
Capabilities
- my-cli:app:unspecified (default capability)
Artifacts
- ../build/child-projects/app/libs/app.jar (artifactType = jar)
I'm not sure how to query for that in the parent projectRobert Elliot
02/11/2025, 4:30 PMapp/build.gradle.kts
val finalArtifacts: Configuration by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts.add(finalArtifacts.name, tasks.bootJar)
build.gradle.kts
val desiredArtifacts: Configuration by configurations.creating {
isTransitive = false
}
dependencies {
desiredArtifacts(
project(mapOf(
"path" to projects.app.path,
"configuration" to "finalArtifacts")
),
)
}
tasks {
val copyArtifacts by registering(Copy::class) {
from(desiredArtifacts)
into(layout.buildDirectory.dir("artifacts"))
dependsOn(":app:assemble")
}
assemble {
dependsOn(copyArtifacts)
}
}Vampire
02/11/2025, 4:35 PMOK, it has a variant, but no attributes?Not fully sure, but I guess then it is not modeled as full-fledged variant with attributes, but you just request the configuration explicitly like
desiredArtifacts(project(path = projects.showcase.path, configuration = "bootArchives"))
or something similarVampire
02/11/2025, 4:36 PMmapOf instead of the type-safe project() overload with named parameters.Vampire
02/11/2025, 4:37 PMdependsOn on the app task should not be necessary either way and indeed is bad practice.
If you need it for the task to run, you maybe connected the task wrongly to the consumable configuration.Robert Elliot
02/11/2025, 4:38 PMRobert Elliot
02/11/2025, 4:39 PMVampire
02/11/2025, 4:39 PMRobert Elliot
02/12/2025, 10:32 AMapp/build.gradle.kts.
Parent `build.gradle.kts`:
val desiredArtifacts: Configuration by configurations.creating {
isTransitive = false
}
dependencies {
desiredArtifacts(project(path = projects.app.path, configuration = "bootArchives"))
}
tasks {
val copyArtifacts by registering(Copy::class) {
from(desiredArtifacts)
into(layout.buildDirectory.dir("artifacts"))
}
assemble {
dependsOn(copyArtifacts)
}
}Vampire
02/12/2025, 10:37 AMdesiredArtifacts configuration is legacy in its configuration as it is resolvable, declarable, and consumable.
The idiomatic way is to have configurations with one of the three roles ("dependency scope" to declare dependencies, "resolvable" to resolve things, "consumable" to expose things to others).
There are incubating helpers to create these (e.g. configuration.resolvable(...)), or you set the isCanBe... properties manually and isVisible to false.
You would then have two configurations, one to declare the dependency that is neither resolvable nor consumable, and one that is resolvable and extends the declarable one that you use for the resolving.
🙂Robert Elliot
02/12/2025, 1:42 PMval desiredArtifactsDeps: Configuration by configurations.dependencyScope("desiredArtifactsDeps") {
isTransitive = false
}
dependencies {
desiredArtifactsDeps(project(path = projects.app.path, configuration = "bootArchives"))
}
val desiredArtifacts: Configuration by configurations.resolvable("desiredArtifacts") {
extendsFrom(desiredArtifactsDeps)
}
tasks {
val copyArtifacts by registering(Copy::class) {
from(desiredArtifacts)
into(layout.buildDirectory.dir("artifacts"))
}
assemble {
dependsOn(copyArtifacts)
}
}Vampire
02/12/2025, 2:08 PM=, not by and in the extendsFrom then a get() to maintain laziness as far as possibleRobert Elliot
02/12/2025, 2:23 PMval desiredArtifactDeps = configurations.dependencyScope("desiredArtifactDeps")
val desiredArtifacts = configurations.resolvable("desiredArtifacts") {
extendsFrom(desiredArtifactDeps.get())
}
dependencies {
desiredArtifactDeps(project(path = projects.app.path, configuration = "bootArchives"))
}
tasks {
val copyArtifacts by registering(Copy::class) {
from(desiredArtifacts)
into(layout.buildDirectory.dir("artifacts"))
}
assemble {
dependsOn(copyArtifacts)
}
}Vampire
02/12/2025, 2:26 PMVampire
02/12/2025, 2:27 PMSync or sync { ... }, not Copy or copy { ... } to make sure there are no stale files. 🙂Robert Elliot
02/12/2025, 2:29 PMReadOnlyProperty failed with some contextual problem I didn't understandd.Vampire
02/12/2025, 2:40 PMRobert Elliot
02/12/2025, 2:44 PMfun ConfigurationContainer.dependencyScope(action: DependencyScopeConfiguration.() -> Unit = {}): ReadOnlyProperty<Any, NamedDomainObjectProvider<DependencyScopeConfiguration>> = ReadOnlyProperty { _, prop ->
dependencyScope(prop.name, action)
}
fun ConfigurationContainer.resolvable(action: ResolvableConfiguration.() -> Unit = {}): ReadOnlyProperty<Any, NamedDomainObjectProvider<ResolvableConfiguration>> = ReadOnlyProperty { _, prop ->
resolvable(prop.name, action)
}
val desiredArtifactDeps by configurations.dependencyScope()
val desiredArtifacts by configurations.resolvable {
extendsFrom(desiredArtifactDeps.get())
}
fails:
FAILURE: Build failed with an exception.
* What went wrong:
Could not determine the dependencies of task ':assemble'.
> Could not create task ':copyArtifacts'.
> NamedDomainObjectContainer#dependencyScope(String, Action) on configuration container for root project 'my-cli' cannot be executed in the current context.Vampire
02/12/2025, 4:09 PMdesiredArtifacts is realized the get() on desiredArtifactDeps is called which now causes the built-in dependencyScope to be called which is too late as the container is already immutable or something like that.
You probably need to use a delegate provider