Despite its name, providers.gradleProperty is reading project properties, and those also only if defined in the root project or up (commandline arguments, system properties, environment variables), but not in subproject-specific gradle.properties files.
The properties plugin is using a trick, it re-reads all properties even the ones Gradle already has read and then sets all values it determined as ext / extra properties.
In former times this was enough for everything as things like project.findProperty or project.getProperty first look in the ext properties for a value and if not found there look in the project properties.
Those new providers do not do that and thus do not see the values determined by the properties plugin.
So you can only use the files read by the properties plugin for things you know are not and will not be read using providers.gradleProperty but using project.findProperty or project.getProperty.
So I either have the configuration cache and the providers you showed me here or the properties-plugin?
The providers are not really the point, especially when having that plugin applied.
The plugin is very old and seemingly unmaintained and does very outdated things.
It completely breaks task-configuration avoidance for all tasks.
And it makes each and every system property and environment variable a configuration cache input.
So applying this plugin effectively disables configuration cache reuse and also makes configuration slow as always all tasks need to be configured.
The only thing left is, that all tasks can run in parallel with configuration cache enabled.
So actually I’d generally disourage the use of that plugin in its current state.
But you could as well get properties with findProperty or getProperty to get the values the plugin has set, that will be CC compatible, just that a change in their value would invalidate the CC entry as you get the value at configuration time. But as the plugin anyway invalidates the CC entry effectively that doesn’t matter anyway as it does not make it worse.
I think my use case does not profit greatly from the CC, so that will go.
That is something you have to measure, CC can make builds faster but also slower, as the CC entry can be reused and all tasks can run in parallel, but dependency downloading happens sequentially in return. But I’d say most builds do benefit from CC if properly used.
fsEnvironment.orNull is actually fsEnvironment.getOrNull() so you are getting the provider at configuration time and that should be avoided if possible. If you only need the value at execution time, only get it at execution time, so that it is not a CC input, otherwise each change in the value invalidates the CC entry. (Unless you use the properties plugin, see above)
Hard to say what is “best”.
Probably also depends on whether this is different for every user or the same for everyone.
Your approach might not be the worst actually, just a bit pimped to not evaluate a provider early, for example like
val fsEnvironment = providers.gradleProperty("fs.environment")
val fsHost = fsEnvironment.flatMap {
providers
.gradleProperty("${it}.fs.host")
.orElse(providers.gradleProperty("fs.host"))
}