Daniele Segato
10/21/2024, 9:43 AMinterface NHMultiplatformPluginExtension {
val iosXcfName: Property<String>
}
I than inject it in my plugin:
val extension = extensions.create("nhMultiplatform", NHMultiplatformPluginExtension::class.java)
extension.iosXcfName.convention("shared")
In my build.gradle.kts I can do:
nhMultiplatform {
iosXcfName.set("my_xfc_name")
}
however I’d like to be able to use it like this:
nhMultiplatform {
iosXcfName = "my_xfc_name"
}
what’s the idiomatic way of doing it?
And another huge issue is that I need to get the value set by the user and use it in my plugin as parameter for another plugin but when I get the property it always return the default value “shared” (if I remove convention it gives an error Cannot query the value of extension 'nhMultiplatform' property 'iosXcfName' because it has no value available.)Niels Doucet
10/21/2024, 9:45 AMDaniele Segato
10/21/2024, 9:57 AMNiels Doucet
10/21/2024, 10:02 AMDaniele Segato
10/21/2024, 10:04 AMclass KotlinMultiplatformLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.multiplatform")
}
configureNHMultiplatformPluginExtension()
configureKotlin()
configureMultiplatformLibrary()
}
}
}
I inject my extension like this:
internal fun Project.configureNHMultiplatformPluginExtension() {
extensions.create("nhMultiplatform", NHMultiplatformPluginExtension::class.java)
}
This is an utility method to extract it
internal fun <T> Project.withNHMultiplatformPluginExtension(action: NHMultiplatformPluginExtension.() -> T): T {
val extension = extensions.getByType<NHMultiplatformPluginExtension>()
return action.invoke(extension)
}
And than:
fun Project.configureMultiplatformLibrary() {
with(this) {
with(pluginManager) {
apply("com.android.kotlin.multiplatform.library")
}
val xcfName = withNHMultiplatformPluginExtension { iosXcfName.get() }
this is where it crash with error because it cannot read the propertyNiels Doucet
10/21/2024, 10:21 AMwith?
And you shouldn't be reading the extension properties during the configuration phase. The consumer has no opportunity to change the value.Daniele Segato
10/21/2024, 10:24 AMDaniele Segato
10/21/2024, 10:27 AMextensions.configure(KotlinMultiplatformExtension::class.java) {
// here
}
in hope that it would be a better place to read it but it didn’t change anything.
The indirection is just to avoid having to write:
val extension = extensions.getByType<NHMultiplatformPluginExtension>()
val xcfName = extension.iosXcfName.get()
I don’t think it matters? does it?Daniele Segato
10/21/2024, 10:29 AMDaniele Segato
10/21/2024, 10:39 AMinterface NHMultiplatformPluginExtension {
fun ios(xcfName: String)
fun android(namespace: String)
}
and inside of those function implementation actually apply the things I need in the other pluginsNiels Doucet
10/21/2024, 10:40 AMDaniele Segato
10/21/2024, 10:41 AMDaniele Segato
10/21/2024, 10:41 AMNiels Doucet
10/21/2024, 10:43 AMNiels Doucet
10/21/2024, 10:44 AMDaniele Segato
10/21/2024, 10:57 AMkotlin {
val xcfName = withNHMultiplatformPluginExtension { iosXcfName.get() }
androidLibrary {
compileSdk = Versions.COMPILE_SDK
minSdk = Versions.MIN_SDK
withAndroidTestOnJvmBuilder {
compilationName = "unitTest"
defaultSourceSetName = "androidUnitTest"
}
withAndroidTestOnDeviceBuilder {
compilationName = "instrumentedTest"
defaultSourceSetName = "androidInstrumentedTest"
sourceSetTreeName = "test"
}.configure {
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
lint {
// Disable lintVital. Not needed since lint is run on CI
// TODO we do not /actually/ run lint on CI yet, but we will
checkReleaseBuilds = false
// Ignore any tests
// ignoreTestSources = true
// Make the build fail on any lint errors
abortOnError = true
}
// isCoreLibraryDesugaringEnabled = true
}
iosX64 {
binaries.framework {
baseName = xcfName
}
}
iosArm64 {
binaries.framework {
baseName = xcfName
}
}
iosSimulatorArm64 {
binaries.framework {
baseName = xcfName
}
}
sourceSets.apply {
commonMain {
dependencies {
implementation(libs.findLibrary("kotlin-stdlib").get())
}
}
commonTest {
dependencies {
implementation(libs.findLibrary("kotlin.test").get())
}
}
getByName("androidInstrumentedTest") {
dependencies {
implementation(libs.findLibrary("androidx-runner").get())
implementation(libs.findLibrary("androidx-core").get())
implementation(libs.findLibrary("androidx-junit").get())
}
}
}
}Daniele Segato
10/21/2024, 11:13 AMkotlin { } configuration is
private fun Project.kotlin(action: KotlinMultiplatformExtension.() -> Unit) {
extensions.configure(KotlinMultiplatformExtension::class.java, action)
}
it’s the 3rd party plugin I need to configureNiels Doucet
10/21/2024, 11:53 AMbaseName = xcfName <-- these assignments, are they properties in the target dsl? if so, instead of evaluating your extensions property, just assign itDaniele Segato
10/21/2024, 11:58 AMDaniele Segato
10/21/2024, 12:00 PMVampire
10/21/2024, 1:38 PMProperty are not meant to be read at configuration time, because of you do, you have the exact same problems you have when using afterEvaluate, that is ordering problems, timing problems, and race conditions.
They should be wired to other `Property`s and only read at execution time.
If you need the value to set a primitive value at configuration time or do some other logic based on it at configuration time, what I recommend is exactly what you suggested yourself. Replace the property by a function and use the value in its body. If calling the function multiple times with different values cannot work properly maybe prevent calling it multiple times. If you want to keep the DSL-y syntax, you can do so by adding a nesting level like foo { bar = 1 } where then foo is a function getting an Action<MyPropertyHolder> as argument.
Regarding = instead of set, this depends on the Gradle version you are using on the consumer side.Daniele Segato
10/22/2024, 9:56 AMkotlin {
val xcf = XCFramework(conf.xcfName)
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = conf.xcfName
xcf.add(this)
}
}
}
and the fact is that I might have to customize what I do for each target on some project