Applying similar sets of dependencies to different source sets, using a custom plugin

Not directly related to your response, but I am still trying to develop an intuition around Gradle’s laziness and what things should be lazy versus what things should not be lazy.

The Code

Suppose I have this plugin

class DynamicSourceSetsPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val objects = project.objects
        val builds = objects.domainObjectContainer(Build::class.java) { name ->
            objects.newInstance(
                Build::class.java,
                name
            )
        }
        project.extensions.add("builds", builds)
        createSourceSets(project)
    }

    private fun createSourceSets(project: Project) {
        @Suppress("UNCHECKED_CAST") val builds =
            project.extensions.getByName("builds") as NamedDomainObjectContainer<Build>
        builds.all {
            val sourceSetContainer = project.extensions.getByType(SourceSetContainer::class.java)
            val sparkVersionFmt = this.sparkVersion.get().replace(".", "")
            val scalaBinaryVersionFmt = this.scalaBinaryVersion.get().replace(".", "")
            val sourceSetName = "mainSpark${sparkVersionFmt}Scala${scalaBinaryVersionFmt}"
            val mainSourceSet = sourceSetContainer.create(sourceSetName) {
                val main = sourceSetContainer.getByName("main")
                java.setSrcDirs(main.java.srcDirs)
                resources.setSrcDirs(main.resources.srcDirs)
            }

            val testSourceSetName = "testSpark${sparkVersionFmt}Scala${scalaBinaryVersionFmt}"
            sourceSetContainer.create(testSourceSetName) {
                val test = sourceSetContainer.getByName("test")
                java.setSrcDirs(test.java.srcDirs)
                resources.setSrcDirs(test.resources.srcDirs)
                compileClasspath.plus(mainSourceSet.output)
                runtimeClasspath.plus(mainSourceSet.output)
            }
        }
    }
}

abstract class Build @javax.inject.Inject constructor(val name: String) {
    abstract val sparkVersion: Property<String>
    abstract val scalaBinaryVersion: Property<String>
}

The Problem

When I apply the plugin, and configure it like so:

builds {
    create("Spark 3.5.0 with Scala 2.13") {
        sparkVersion.set("3.5.0")
        scalaBinaryVersion.set("2.13")
    }

    create("Spark 3.5.0 with Scala 2.12") {
        sparkVersion.set("3.5.0")
        scalaBinaryVersion.set("2.12")
    }
}

I encounter the follow error:

Cannot query the value of property ‘sparkVersion’ because it has no value available.

I understand what the message is telling me, that sparkVersion and scalaBinaryVersion are not known during the evaluation and configuration phases of the project’s lifecycle and I am trying to access them in my plugin by using Property#get() before the value has been set. The thing is that I need those values in order to dynamically create my source sets.

Beyond using Project#afterEvaluate, I am not sure how to overcome this issue.

Side Note: If I swap builds.all for builds.configureEach, no observable changes happen.

Do you have any suggestions? Any solutions?