Humphrey
08/29/2023, 11:31 AMVampire
08/29/2023, 11:52 AMorg.gradle.jvm.version
(TargetJvmVersion
) to define with which Java version a lib is compatible and which you need. You can either use a component metadata rule to overwrite the attribute of the library so that it is defined as Java 11 compatible, or you set the attribute to 17 on your dependency, to get the version compatible with Java 17, as you know better than Gradle in this case that is compatible with your intended usage. I'd probably prefer the latter in your case.Humphrey
08/29/2023, 12:03 PMorg.gradle.jvm.version
to 17 in my gradle.properties? Will this affect all dependencies then? I just want this single dependency to be added without errors. (maybe an attribute on the dependency itself)?Vampire
08/29/2023, 12:07 PMin my gradle.propertiesI did not say anything like that. I said set an attribute on your dependency.
Vampire
08/29/2023, 12:08 PMVampire
08/29/2023, 12:08 PMVampire
08/29/2023, 12:08 PMHumphrey
08/29/2023, 12:08 PMAdam S
08/29/2023, 1:03 PMimplementation("...")
? But then you don't use the compiled JVM library, just a JSON config file?
If so, it would make sense to create a new Configuration specifically for this dependency - then you wouldn't be dependent on an existing Configuration which specifies it needs JVM 17 codeAdam S
08/29/2023, 1:15 PMHumphrey
08/30/2023, 4:25 AMHumphrey
08/30/2023, 11:57 AMAdam S
08/30/2023, 11:58 AMopenApiSpec
configuration, if you re-ran the task then the old files would still be in the destination dir.Humphrey
08/30/2023, 11:59 AMAdam S
08/30/2023, 12:00 PMAdam S
08/30/2023, 12:02 PMAdam S
08/30/2023, 12:05 PMAdam S
08/30/2023, 12:19 PMAdam S
08/30/2023, 12:20 PMResolvedArtifactResult.id
, and trying to cast it to ModuleComponentIdentifier
. This way it doesn't matter what the file is called - it only matters what the Maven coordinates are. But this isn't critical, and using the file name also works.Humphrey
08/30/2023, 12:21 PMAdam S
08/30/2023, 12:21 PMHumphrey
08/30/2023, 12:30 PMopenApi.incomming.artifactView{}.artifacts
or can I use the artifacts directly without View?
openApi.incomming.artifacts
What is the difference? (gradle 8.3 and kotlin 1.9.10)Humphrey
08/30/2023, 12:31 PMartifacts.filter {}
code. Seems that is not an option?Adam S
08/30/2023, 12:31 PMHumphrey
08/30/2023, 12:37 PM.map { artifacts ->
artifacts
.filter {
val id = (it.id as? ModuleComponentIdentifier) ?: return@filter false
id.module.endsWith("spec")
}
}
)
}
lambda (should be artifact instead of artifacts right?) still it's not happy with the filter on it.Humphrey
08/30/2023, 12:41 PMHumphrey
08/30/2023, 12:53 PMconfigurations.create("openApiSpec") {
isCanBeResolved = true
isCanBeConsumed = false
}
tasks {
register("resolveOpenApiSpecDep", Sync::class) {
from(configurations.getByName("openApiSpec")
.incoming
.artifactView { }
.artifacts
.resolvedArtifacts
.map { x -> x.filter {
val id = (it.id as? ModuleComponentIdentifier) ?: return@filter false
id.module.endsWith("specs") || id.module.endsWith("api")
} }
)
into(layout.buildDirectory.dir("extracted"))
}
}
Humphrey
08/30/2023, 12:54 PMAdam S
08/30/2023, 12:55 PMresolveOpenApiSpecDep
it doesn't download any files, and build/extracted
is empty?Humphrey
08/30/2023, 12:56 PMAdam S
08/30/2023, 12:56 PMAdam S
08/30/2023, 12:57 PMopenApiSpec.incoming
.artifactView {}
.artifacts
.resolvedArtifacts
.map { artifacts ->
artifacts
.filter {
val id = (it.id as? ModuleComponentIdentifier) ?: return@filter false
id.module.endsWith("spec")
}.map {
it.file
}
}
Humphrey
08/30/2023, 1:03 PMAdam S
08/30/2023, 1:04 PMAdam S
08/30/2023, 1:04 PMmap {}
Humphrey
08/30/2023, 1:14 PM.map { x ->
x.filter {
println("filter: ${it.id}")
val id = (it.id as? ModuleComponentIdentifier) ?: return@filter false
println("filter2: $id")
I only get output for the first filter and not for the filter2Adam S
08/30/2023, 1:20 PMHumphrey
08/30/2023, 1:20 PMHumphrey
08/30/2023, 1:21 PMAdam S
08/30/2023, 1:21 PMAdam S
08/30/2023, 1:22 PMit.id
and cast to it (which will probably be complicated!), or use it.id.displayName
and try doing some string matching on that.Humphrey
08/30/2023, 1:23 PMHumphrey
08/30/2023, 2:03 PMHumphrey
08/30/2023, 2:08 PMfrom(configurations.getByName("openApiSpec")
.incoming
.artifacts
.resolvedArtifacts
.map {
x -> x.map{ it.file }
}
I do get the downloaded jar file in the extracted directory..Humphrey
08/30/2023, 2:16 PMtasks {
register("resolveOpenApiSpecDep", Sync::class) {
group = "openapi tools"
from(configurations.getByName("openApiSpec")
.incoming
.artifacts
.resolvedArtifacts
.map { resolvedArtifactResults ->
resolvedArtifactResults.map { it.file }
}
)
into(layout.buildDirectory.dir("downloaded"))
}
}
Humphrey
08/30/2023, 2:17 PMAdam S
08/30/2023, 2:56 PMHumphrey
08/30/2023, 2:57 PMHumphrey
08/30/2023, 2:59 PMsourceSets {
main {
resources {
srcDirs(tasks.getByName("resolveOpenApiSpecDep", Sync::class).destinationDir)
}
}
}
I think something like this is a way to get the desination folder on the classpath, but the task should give more than one that I need to loop over it.Adam S
08/30/2023, 3:03 PMAdam S
08/30/2023, 3:10 PMAdam S
08/30/2023, 3:15 PMHumphrey
08/30/2023, 3:33 PMAdam S
08/30/2023, 3:35 PMkotlin {
sourceSets {
main {
resources.srcDir(prepareOpenApiSpecs)
}
}
}
That way Gradle will know it needs to run the task, and then use the output directory of the task as a resource directoryHumphrey
08/30/2023, 3:37 PM$build/openApiSpecs/<jarfilename>
which is very good. (need to apply that regex to strip that version and extenstion. But it works.Adam S
08/30/2023, 3:37 PMHumphrey
08/30/2023, 3:39 PMVampire
08/30/2023, 5:45 PMHumphrey
08/30/2023, 6:15 PMVampire
08/30/2023, 6:20 PMHumphrey
08/30/2023, 10:42 PMVampire
08/30/2023, 10:47 PMSync
task, but it will then be as trivial as from(configuration.foo); into(whatever)
.Humphrey
09/01/2023, 8:10 AMval openApiSpec by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
tasks {
register("prepareOpenApiSpecs") {
group = "openapi tools"
val archives = serviceOf<ArchiveOperations>()
val fileOps = serviceOf<FileSystemOperations>()
outputs.dir(layout.buildDirectory.dir("openApiSpecs"))
doLast {
fileOps.sync {
val deps = openApiSpec.incoming.files
for (dep in deps) {
from(archives.zipTree(dep)) {
val name = Regex("^.+(?=-)").find(dep.name)!!.value
into(name)
}
}
into(layout.buildDirectory.dir("openApiSpecs"))
}
}
}
getByName(openApiGenerate.name).dependsOn("prepareOpenApiSpecs")
}
openApiGenerate {}
• This seems to do everything I wanted in one task
• Not sure if the outputs is needed and can be omitted?Vampire
09/01/2023, 8:17 AMHumphrey
09/01/2023, 8:18 AMdoLast
an bad practice?Vampire
09/01/2023, 8:18 AMVampire
09/01/2023, 8:19 AMdoLast
per se is fineVampire
09/01/2023, 8:40 AMserviceOf
is not public API
• You use ArchiveOperations
and FileSystemOperations
to avoid using the Project
alternatives, but you do not do the same for ProjectLayout
• You do not declare your inputs, you should always declare all inputs and outputs of a task properly, only then the up-to-date check can properly work and some safety checks like tasks that use outputs of other tasks wihtout properly being wired and so on
• You have the reference to the openApiGenerate
task, then you get its name, just to get the task again by name. This is an unnecessary round-trip and also you disturb task-configuration avoidance by forcing its realisation. If you would want to do that - which you shouldn't -, do it like openApiGenerate { dependsOn(...) }
• Don't depend on a task by String
when you already have a reference to it or a task provider for it (return value of register
, but depend on that instance directly, if you need to do it, which you shouldn't
• Don't use explicit dependsOn
unless on the lefthand side is a lifecycle task. Instead wire task outputs to task inputs. So if you properly declared the outputs of the task, and the openApiGenerate
task properly declares the inputs it has, if you then properly wire the tasks inputs and outputs together, you automatically get the necessary task dependency where needed explicitly.
• The task is not configuration cache safe
• I still think an artifact transform would be more appropriateHumphrey
09/01/2023, 9:03 AMid("org.openapi.generator")
and I can't declare inputs for that task.Humphrey
09/01/2023, 9:07 AMtasks {
register("prepareOpenApiSpecs") {
group = "openapi tools"
inputs.files(openApiSpec.incoming.files)
outputs.dir(layout.buildDirectory.dir("openApiSpecs"))
doLast {
sync {
for (dep in openApiSpec.incoming.files) {
from(zipTree(dep)) {
val name = Regex("^.+(?=-)").find(dep.name)!!.value
into(name)
}
}
into(layout.buildDirectory.dir("openApiSpecs"))
}
}
}
openApiGenerate.get().dependsOn("prepareOpenApiSpecs")
}
Vampire
09/01/2023, 9:14 AMI've tried the artifact transform and it was unzippnig somewhere in the gradle cache directoryYes, as I said, taht's expected and not a problem at all usually. If you really need the file in your project, you could still have a simple
Sync
tasks that copies them where you want them, but most often this is simply not necessary, but you can just use the files from where they are. You should not configure paths to those files anyway, but wire things together instead and it will just take the files from where they are.
• I should use unZip functionallity instead of serviceOf()? Thought that this was better because Adam came up with it.Well, @Adam S unfortunately is not right here,
serviceOf
as I said is an internal utility that you should not use in your build. If you use a proper task class instead, you can just @Inject
those services.
• How do I use the project alternatives (ArchiveOperations and FileSystemOperations)Just using
sync { ... }
, or zipTree { ... }
does it, but then again makes your task even less configuration cache compatible.
• What should I declare as inputs and outputs in this case?Everything the task uses as inputs and produces as outputs. So in your case,
inputs.files(openApiSpec).withProeprtyName("inputFiles")
(the latter part is only for better log and error output)
and I can't declare inputs for that task.You can the same way you do it for you ad-hoc task, but you shouldn't need to. The task should declare which of its properties are inputs and outputs already and you just need to set the values properly by wiring your task outputs to their inputs.
I've made some changes on your comment above:Partly. You still are CC incompatible if that is a concern, your input is not optimal, you still distrub task-configuration avoidance by using
openApiGenerate.get()
, and you still have the explicit task dependency.Adam S
09/01/2023, 11:46 AMcould you take a look and give your opinion about the following approach (tried to combine the better pieces of what Adam made to a simpeler solution.Good job! Generally looks good, but yeah, register the input files as a file input. The rest is just tidying so that all the task logic is associated together
val openApiGenerate = tasks.register("openApiGenerate") {
// Register the task dependency.
// (if you use the top-level tasks.register then you can
// access other top-level variables out-of-order)
dependsOn(prepareOpenApiSpecs)
// (rest of task config...)
}
val prepareOpenApiSpecs = tasks.register("prepareOpenApiSpecs") {
group = "openapi tools"
val archives = serviceOf<ArchiveOperations>()
val fileOps = serviceOf<FileSystemOperations>()
outputs.dir(layout.buildDirectory.dir("openApiSpecs"))
// register the files as an input so that Gradle knows
// if it needs to re-run the task when the files change
val openApiSpecFiles = openApiSpec.incoming.files
inputs.files(openApiSpecFiles).withPropertyName("openApiSpecFiles")
doLast {
fileOps.sync {
for (dep in openApiSpecFiles) {
from(archives.zipTree(dep)) {
val name = Regex("^.+(?=-)").find(dep.name)!!.value
into(name)
}
}
into(layout.buildDirectory.dir("openApiSpecs"))
}
}
}
Not sure if the outputs is needed and can be omitted?From the point of view of running the task, no, it's not strictly necessary. Gradle will run tasks that are not configured correctly. However, it's best to set up the inputs/outputs for a few reasons: Like Gradle will check to make sure other tasks don't have conflicting outputs, which helps avoid mistakes; or thanks to Build Cache and Task Avoidance Gradle will be able to realise tasks are up-to-date, which will make your builds way quicker
Humphrey
09/10/2023, 9:13 AMVampire
09/10/2023, 11:04 AMHumphrey
09/10/2023, 12:44 PMHumphrey
09/10/2023, 12:45 PMVampire
09/10/2023, 8:47 PM