Kirill Gavrilov
03/22/2024, 10:39 AMexec {}
to providers.exec {}
to enable configuration cache and get rid of its warnings.
How can I get stdin and stdout both merged in one stream from providers.exec
? an attempt to call setXxxOutput()
which I used previously throws "Standard streams cannot be configured for exec output provider"
Posted this question with code samples on SOAdam
03/22/2024, 11:24 AMproviders.exec {}
returns a result, so you can get the text (or bytes, using asBytes()
)
val execResult = providers.exec { commandLine("echo", "hello") }
println("stdout " + execResult.standardOutput.asText.orNull)
println("stderr " + execResult.standardError.asText.orNull)
Adam
03/22/2024, 11:24 AMAdam
03/22/2024, 11:27 AM@Inject
a ExecOperations
service, and then you can just call execOperations.exec {}
and it'll work the same as Project.exec {}
Adam
03/22/2024, 11:29 AMKirill Gavrilov
03/22/2024, 11:45 AMKirill Gavrilov
03/22/2024, 1:05 PMExecOperations
service works for aggregating stdin and stdout into a single stream and its .exec
configuration-cache-friendly. Thank you!
Now I'm struggling with converting JavaExec
task the same way to ExecOperations.javaExec()
. I'm getting:
- Task `:ktlint` of type `org.gradle.api.DefaultTask`: cannot serialize Gradle script object references as these are not supported with the configuration cache.
See <https://docs.gradle.org/8.6/userguide/configuration_cache.html#config_cache:requirements:disallowed_types>
While I can't find any more details, I tend to think that the root cause of the problem here is that I reference one of my "configurans" as a classpath
for ExecOperations.javaExec()
. Do you have any suggestion in this situation? My code is as simple as (yeah, I'm aware of a Gradle plugin for Ktlint, but had a preference to build my own tasks which I want to make configuration-cache friendly now):
register("ktlint") {
val exec = serviceOf<ExecOperations>()
doLast {
val myExec = exec.javaexec {
classpath = ktlint
mainClass = "com.pinterest.ktlint.Main"
args(*ktlintTargets)
standardOutput = System.out
errorOutput = System.err
}
myExec.assertNormalExitValue()
}
}
Adam
03/22/2024, 2:17 PMAdam
03/22/2024, 2:21 PM// script level val
val ktlint by configurations.creating { ... }
tasks.register("ktlint") {
val ktlintClasspath = ktlint.incoming.files // redefine the val for CC, and use a CC compatible type (FileCollection)
// ...
}
Adam
03/22/2024, 2:25 PMval ktlint by configurations.creating { ... }
tasks.register("ktlint") {
val ktlintClasspath = ktlint.incoming.files
inputs.files(ktlintClasspath).withPropertyName("ktlintClasspath")
.withNormalizer(ClasspathNormalizer::class)
doLast {
exec.javaexec {
classpath = ktlintClasspath
// ...
}
}
}
Kirill Gavrilov
03/22/2024, 3:51 PM:ktlint
of type `org.gradle.api.DefaultTask`: cannot serialize object of type 'org.gradle.api.internal.artifacts.configurations.*DefaultUnlockedConfiguration*', a subtype of 'org.gradle.api.artifacts.Configuration', as these are not supported with the configuration cache.
My configuration is created as simply as:
val ktlint: Configuration by configurations.creating
Adam
03/22/2024, 4:20 PMval ktlintClasspath = ktlint
? If so, try val ktlintClasspath = ktlin.incoming.files
.Adam
03/22/2024, 4:21 PM.incoming.files
converts it to a FileCollectionKirill Gavrilov
03/22/2024, 4:24 PMval ktlintClasspath = ktlin.incoming.files
worked out! Thank you!Adam
03/22/2024, 4:28 PMKirill Gavrilov
03/22/2024, 4:30 PMAdam
03/22/2024, 4:31 PMAdam
03/22/2024, 4:32 PMKirill Gavrilov
03/22/2024, 5:56 PMJavaExec
, it's not solved if I just want to execute arbitrary process during build configuration (without any tasks involved at all). The CC does not allow me to use ExecOperations service outside of the task, so I have to rely on recommended `providers.exec`(with which I have troubles with at merging 2 stream into one, it's simply not supported).
My use case for executing arbitrary process on Gradle's configuration-phase is pretty simple btw. I have a dumb Gradle project in monorepo's root, so whenever new people open it with Intellij for the 1st time to contribute, Intellij will trigger Gradle, which in turn `exec`s some plain git commands to initialize mandatory git hooks.
something like this:
fun tryToInitGitHooks() {
val isGitRepoCheck = executeCommand("git", "rev-parse", "--git-dir")
if (isGitRepoCheck.exitCode != 0) {
logger.info("Seems like not a git repo, skipping git hooks initialization")
logger.info("Here is the result of git repo check. Exit code: {}. stdout:", isGitRepoCheck.exitCode)
logger.info(isGitRepoCheck.consoleOutput) // <---- Here is where I need stdin and stderr lines merged together in the right order
return
}
// ... success, execute more commands to init git hooks...
}
data class CommandExecutionResult(val exitCode: Int, val consoleOutput: String)
fun executeCommand(vararg args: String): CommandExecutionResult {
ByteArrayOutputStream().use {
val execResult = exec {
commandLine(*args)
isIgnoreExitValue = true
standardOutput = it
errorOutput = it
}
return CommandExecutionResult(exitCode = execResult.exitValue, consoleOutput = it.toString())
}
}
@Adam any ideas how can I solve this supporting CC? Maybe I can make Gradle to execute one mandatory task if no tasks was requested explicitly? In this case I would just wrap those plain git commands as a task and Intellij would execute that on project import... I'm just not aware if that's possible at all. Or maybe Intellij does execute some tasks on project import? In this case I could add finalizedBy
dependency to Intellij's import task š¤Adam
03/22/2024, 6:38 PMProject.exec {}
work during configuration?Adam
03/22/2024, 6:38 PMKirill Gavrilov
03/22/2024, 6:45 PMVampire
03/22/2024, 10:24 PMserviceOf
is internal API, you should not use it
⢠ktlin.incoming.files
is not necessary, a Configuration
is-a FileCollection
already. You just need to specify the type of the variable explicitly to be FileCollection
and it already is CC-safe. If you do not specify a type, it gets the Configuration
type implicitly and that is not CC-safe.
⢠If you need to run external process at configuration time, you need to use a ValueSource
. This is then also always executed, whether CC entry is reused or not and even twice if CC entry is not reused.