I've got a subproject, `:app`, which has the sprin...
# dependency-management
r
I've got a subproject,
: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...
Root build code:
Copy code
val 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)
  }
}
Subproject build code:
Copy code
artifacts {
  add("archives", tasks.bootJar)
}
But no dice. I only get the jar built by the
jar
task, not the one built by the
bootJar
task
v
If the boot plugin does not register an outgoing variant for the boot jar, you either want to create a full outgoing variant that you request, or as it is just for sharing inside the same build, just a configuration that you then explicitly request from the depending project.
You could check with
gradlew :app:outgoingVariants
whether there maybe is already a variant declared for the boot jar that you could just request.
Here are the docs that show both approaches if the boot plugin did not already create an outgoing variant: https://docs.gradle.org/current/userguide/cross_project_publications.html
r
Superb, thanks. It creates a variant
bootArchives
👌 1
v
It also shows you the artifacts that the variant has, so request those attributes and you should get that variant. You can do that on the
desiredArtifacts
configuration, or on the concrete dependency.
r
OK, it has a variant, but no attributes?
Copy code
--------------------------------------------------
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 project
OK, I've bailed out - I know it's not recommended but this 1) works 2) is fairly concise 3) I undestand it:
app/build.gradle.kts
Copy code
val finalArtifacts: Configuration by configurations.creating {
  isCanBeConsumed = true
  isCanBeResolved = false
}

artifacts.add(finalArtifacts.name, tasks.bootJar)
build.gradle.kts
Copy code
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)
  }
}
v
OK, 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
Copy code
desiredArtifacts(project(path = projects.showcase.path, configuration = "bootArchives"))
or something similar
What you showed last is probably just creating an identical sibling configuration, and uses
mapOf
instead of the type-safe
project()
overload with named parameters.
The explicit
dependsOn
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.
r
Yup, that all works, much nicer, thanks
I didn't need it once gradle was actually finding the artifact I wanted
v
👌
r
Final version for anyone searching this in the future... No change needed in
app/build.gradle.kts
. Parent `build.gradle.kts`:
Copy code
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)
  }
}
👌 1
v
One nit in case you are not aware and are interested. Your
desiredArtifacts
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. 🙂
r
Like this?
Copy code
val 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)
  }
}
v
Yes, while I would there use
=
, not
by
and in the
extendsFrom
then a
get()
to maintain laziness as far as possible
👍 1
r
Shame they haven't created the property delegates to derive the name from the val yet.
Copy code
val 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)
  }
}
v
Unfortunately that was a intended decision and they even consider removing the other delegates: https://github.com/gradle/gradle/issues/27204
Btw. one more nit, in most situations you want
Sync
or
sync { ... }
, not
Copy
or
copy { ... }
to make sure there are no stale files. 🙂
r
Odd call... though mind you my brief attempt to do it myself with
ReadOnlyProperty
failed with some contextual problem I didn't understandd.
v
I didn't get that sentence, maybe due to my lack of English. 😞
r
I'm surprised they rejected your request. However, I tried to implement the feature myself and failed:
Copy code
fun 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:
Copy code
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.
v
Ah, I see. I don't remember exactly. But I think it is because when now the
desiredArtifacts
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