Colton Idle
01/15/2025, 4:00 PMThomas Broyer
01/15/2025, 4:08 PMColton Idle
01/15/2025, 4:10 PMColton Idle
01/15/2025, 4:12 PMColton Idle
01/15/2025, 4:12 PMThomas Broyer
01/15/2025, 5:03 PMColton Idle
01/15/2025, 7:03 PMtasks.register("getCommitMessages") {
doLast {
println("print release notes")
...
}
}
it fails and complains that
ANDROID_KEYSTORE_FILEPATH
env var is missing. But getCommitMessages doesn't ever call any env vars.
But elsewhere in the script (in the android block) we request ANDROID_KEYSTORE_FILEPATH
in the signingConfigs block. So I'm just trying to sanity check why kts is resolving/compiling the whole file.Thomas Broyer
01/15/2025, 7:16 PMColton Idle
01/15/2025, 7:42 PMsigningConfigs {
create("release") {
storeFile = file(System.getenv(["ANDROID_KEYSTORE_FILEPATH"])
storePassword = "password"
keyAlias = "staging"
keyPassword = "password"
}
}
Colton Idle
01/15/2025, 7:43 PMChris Lee
01/15/2025, 7:44 PMlazy
reference to defer accessing environment variables until they are needed (if at all).Colton Idle
01/15/2025, 7:46 PMThomas Broyer
01/15/2025, 7:46 PMregister("release")
rather than create("release")
(again, I don't know the AGP DSL, so it might not even exist)
Generally speaking, using create()
means the associated configuration block is executed before the build runs (think of it as executing immediately), whereas using register()
defers execution of that block to when it's known to be needed.
See https://docs.gradle.org/current/userguide/task_configuration_avoidance.html and https://docs.gradle.org/current/userguide/lazy_configuration.htmlChris Lee
01/15/2025, 7:46 PMGradle provides a number of environment variables, which are listed below. Environment variables can be retrieved lazily using.providers.environmentVariable()
Chris Lee
01/15/2025, 7:47 PMSystem.getenv
, happens at configuration time across all the tasks (not just the ones that will be run) - switching them to a provider defers resolving the env var (and makes things compatible with the configuration cache).Colton Idle
01/15/2025, 7:47 PMColton Idle
01/15/2025, 7:49 PMColton Idle
01/15/2025, 7:50 PMChris Lee
01/15/2025, 7:50 PMChris Lee
01/15/2025, 7:51 PMPlugins and build scripts may read system properties and environment variables directly at configuration time with standard Java, Groovy, or Kotlin APIs or with the value supplier APIs. Doing so makes such variable or property a build configuration input, so changing the value invalidates the configuration cache. The configuration cache report includes a list of these build configuration inputs to help track them.
In general, you should avoid reading the value of system properties and environment variables at configuration time, to avoid cache misses when value changes. Instead, you can connect the `Provider`returned by providers.systemProperty() or providers.environmentVariable() to task properties.https://docs.gradle.org/current/userguide/configuration_cache.html
Chris Lee
01/15/2025, 7:52 PMColton Idle
01/15/2025, 7:54 PMsomething else got adjusted in translation to make the resolution eager. Its often hard to tell from Groovy what actual API call happens on the backend, so translating one-to-one can be challenging.interesting. i will take another look, but thats actually exactly what i was looking for. that it was always eager and its not just a groovy vs kts thing
Colton Idle
01/15/2025, 7:54 PMChris Lee
01/15/2025, 7:59 PMprintln
at the root level of script happens here (at inner levels it would be subject to when callbacks get invoked)
• configuration time - really the results of executing the script, as noted above - what is needed to be configured is executed;
• execution time - the necessary tasks are executedephemient
01/16/2025, 5:09 AMSystem.getenv
returns a nullable string, but it's possible that Kotlin is enforcing nullability checking on APIs where Groovy does notephemient
01/16/2025, 5:11 AMproviders.environmentVariable
is lazyephemient
01/16/2025, 5:20 AMsigningConfigs {
create("release") {
val storeFilePath = System.getenv("ANDROID_KEYSTORE_FILEPATH")
if (storeFilePath.isNullOrEmpty()) return@create
storeFile = file(storeFilePath)
storePassword = "password"
keyAlias = "staging"
keyPassword = "password"
}
}
for tasks that don't use release signing, it won't matter if it's unconfiguredColton Idle
01/16/2025, 5:32 AMoooh. interesting. so i can't actually usereturns a nullable string, but it's possible that Kotlin is enforcing nullability checking on APIs where Groovy does notSystem.getenv
providers.environmentVariable
in android signing?ephemient
01/16/2025, 8:00 AMColton Idle
01/16/2025, 9:11 PMVampire
01/16/2025, 11:38 PMfor a second i thought maybe kts precompiles things or something since its "static" vs dynamic "groovy"That's actually irrelevant. Yes, Kotlin code needs to be compiled of course. I think - but am not sure right now - the Groovy code is also compiled, so that it does not need to be interpreted each time it is run if it did not change. But whether you compile it or not before you run it is irrelevant.
System.getenv(...)
will never be executed at compilation time, that would be extremely strange, wrong, strange, lead to wrong results, and strange.
If there is a difference, then as the others said maybe because in Groovy something was lazy which it is no longer in Kotlin, while it usually is the other way around due to backwards compatibility.
For example if you have
tasks.foo {
...
}
this is eager in Groovy but lazy in Kotlin.
I'm not right now aware of a case where it is the other way around. But especially with Android everything is possible, as Android is always a bit special. It's really hard to tell what the difference might be without having both versions and looking hands-on.
oooh. interesting. so i can't actually useSure you can, just does not make much sense if you need the configuration at configuration time because the property you configure is not a lazy Gradle property. If what you configure there is a https://developer.android.com/reference/tools/gradle-api/8.3/com/android/build/api/dsl/SigningConfig, then thein android signing?providers.environmentVariable
storeFile
property is a plain File
so whether you use System.env
or providers.environmentVariable
makes no difference, you can use both. But you need the value at configuration time and it will be a configuration cache input if you use CC.
That signing configs block exists inside of the android block. while the tasks.register exists inside of the buildTypes block, which is inside of the android block.The block you showed will not even compile in Kotlin. In Groovy it will compile but fail at runtime as
ArrayList
cannot be cast to String
and parentheses are not balanced.
The call itself will either way not be the cause of the difference, but the context in which it happens, but it shows that any advice that can be given based on the examples you show are just wild guesses as you did not show the actual code. 😉
Maybe before you had the release
signing config lazily registered and now by doing the create
call it is immediately realized and configured instead of when needed first like mentioned already. Or you changed something else that causes now that part of the configuration to be executed which can be a totally different and unrelated part. For example if you now somewhere cause all tasks to be realized and one of the configured task uses that configuration and realizes it when in the old version the task was not configured and so that System.getenv
call never been done, ...
But as I said, without the full and correct code hands-on it is hard to guess where your behavior change is coming from.Colton Idle
01/21/2025, 5:20 AMephemient
01/21/2025, 6:04 AMreturns a nullable string, but it's possible that Kotlin is enforcing nullability checking on APIs where Groovy does notSystem.getenv
ephemient
01/21/2025, 6:04 AMfile()
takes a non-null argumentephemient
01/21/2025, 6:05 AM"null"
if the environment variable doesn't exist. in Kotlin you aren'tephemient
01/21/2025, 6:07 AMstoreFile = file("${System.getenv("KEYSTORE_FILE")}")
or
storeFile = file(System.getenv("KEYSTORE_FILE").toString())
which would then have the same behavior as your Groovy script, or just don't set it at all if not present,
System.getenv("KEYSTORE_FILE")?.let { storeFile = file(it) }
or
storeFile = System.getenv("KEYSTORE_FILE")?.let(::file)
Vampire
01/21/2025, 8:50 AMFile("release.txt")
(resp. new File("release.txt")
).
Using the File
constructor (and similar things like FileInputStream
) with a relative path resolves to the current working directory.
This often is the root project directory but it is not guaranteed and also often not the case, but the IDE installation directory, or the daemon log directory, ...
Almost any JVM code, not only Gradle build logic, that depends on the current working directory is broken, because if the user calls the program from a different directory it will not work as expected.
The only use-case I'm aware where this is appropriate is, to resolve a relative path given by the user on the commandline as you can then safely assume he means relative to the current working directory and in the Gradle case even that would not be right, as the working directory of the daemon has nothing to do with the working direcotry of the user, even though it often is the same.Colton Idle
01/21/2025, 1:39 PMephemient
01/21/2025, 1:43 PMColton Idle
01/21/2025, 3:44 PMfun getProperty(propertyName: String) : String {
val prop = project.properties[propertyName]
?: gradleLocalProperties(rootDir, providers).getProperty(propertyName)
?: System.getenv()[propertyName]
?: throw IllegalStateException("Add a local property or env variable")
return prop
}
but that seemed to cause even more issues. 🙃Chris Lee
01/21/2025, 3:48 PMfile("${System.getenv("KEYSTORE_FILE")}")
== file("null")
. Obviously incorrect, saved by the fact that those tasks aren’t run and try to access that file.
One could use the lazy properties (where available) or set the value to null (storeFile = null) where the env var doesn’t exist.Thomas Broyer
01/21/2025, 5:30 PMfile(System.env.KEYSTORE_FILE)
in Groovy, and you'd have had the same problem as in Kotlin.
So either you translate to Kotlin with the same behavior (wrap in a string), or you fix it using the suggestions from above (System.get("KEYSTORE_FILE")?.let(::file)
)Colton Idle
01/21/2025, 5:47 PM