This message was deleted.
# community-support
s
This message was deleted.
z
I don’t need to support multiple configuration points:
Copy code
config {
  // configure my convention plugin
}

// it's fine if this throws - and my convention plugin logic runs immediately after the `config` lambda above ends
c
use a function to configure the extension, hooking the logic for post-configuration into the method (after configuration)
pseudo-code:
Copy code
fun Project.config(block : (MyExtension).() -> Unit ) {
    val ext = <lookup extension>
    ext.apply(block)
    // do magic here
}
1
z
so that’s kind of what I’m doing - but I want to have this behavior:
Copy code
plugins {
  id("me.conventions.library.android")
}
config { // emits `MyAndroidLibraryConventionExtension`
}
Copy code
plugins {
  id("me.conventions.app.android")
}
config { // emits `MyAndroidAppConventionExtension`
}
where the same wording of
config { }
lambda emits a different value based on the convention plugin applied
I want to avoid the following (different function names):
Copy code
plugins {
  id("me.conventions.library.android")
}
configLibrary { // emits `MyAndroidLibraryConventionExtension`
}
Copy code
plugins {
  id("me.conventions.app.android")
}
configApp { // emits `MyAndroidAppConventionExtension`
}
j
I think you can't if you don't want to have it in the same package (root)
z
yeah, I want it in the root package
I need like a
preAfterEvaluate { }
lol
j
your approach is similar to slack plugin one, but without merging the plugins and keeping convention plugins
I would go for the slack approach tbh
z
where is that approach described? Think I remember some article being posted about it
j
Copy code
plugins {
  id("me.convention")
}

config {
    android {
        library { ... }
        // app { ... }
    }
}
with the config, android, library... extensions you collect the state, you emit an error if there is an inconsistent state, if the state is valid, you apply the plugins, in that case you apply android library plugin and the android block provides a custom dsl for android
Copy code
plugins {
  id("me.convention")
}

config {
    android {
        features {
            dagger()
        }
        
        library()
    }
}
z
so I don’t like that particularly because it groups everything under 1 plugin and makes the following throw (no default):
Copy code
plugins {
  id("me.convention")
}

config {
}
I wonder if we can control what accessors gradle generates when a plugin is applied, like how it generates an accessor automatically for any extension defined
c
those accessors are named after the extension name. e.g.
project.extensions.create<Something>("foo")
creates
foo
accessor.
👍 1
z
Copy code
plugins {
  id("me.convention")
}

config {
}

// the problem is that I need a hook to run here, immediately before `afterEvaluate { }` - which gradle does not seem to provide
c
yep. as above:
Copy code
fun Project.config(block : (MyExtension).() -> Unit ) {
    val ext = <lookup extension>
    ext.apply(block)
    // do magic here
}
don’t use the generated accessor, create your own.
I want to emit different values in the lambda depending on the convention plugin applied - like AGP
i.e. just
config { }
- not
configLibrary { }
/
configApp { }
c
elsewhere I’m letting other plugins add to
config
, such that the DSL inside config is extensible.
Copy code
config {
    library {
    
    }
}
…leaving the root extension to handle aggregating the configuration and doing the post-configuration notification.
z
Copy code
config {
    library {
    
    }
}
I’d want
library
to fail compilation (as opposed to a runtime error) if we are working within the
<http://convention.app|convention.app>
plugin
I’m normally fine with workarounds, but this would be a public API that affects hundreds of subprojects
c
like this?
Copy code
plugins {
    id("my.rust.plugin")
}

cloudshift {
    golang {

    }
    rust {

    }
}
…this would fail to compile as
my.golang.plugin
hasn’t been applied (which would register the golang extension).
z
how do you register an extension on an extension?
c
extensions are ExtensionAware. So ext.extenstions.create.
Copy code
extensionAware.extensions.create<GoLangExtension>("golang")
z
that would still allow this to compile though, right:
Copy code
plugins {
    id("my.rust.plugin")
}

golang {

}
rust {

}
cloudshift {
    
}
which isn’t ideal
c
no. those extensions are only registered in
cloudshift
.
j
if you follow the slack way, you can save the complete state of what the user want to build, so if the user applies a wrong configuration, for example
library
+
app
, you can know that state and emit an error in compile time with a custom message indicating that
library
with
app
can't work
you are not forced to nest it, you can do
Copy code
library { }

app { }
z
Does that mean you can do the following then:
Copy code
cloudshift.goLang {
}
j
and recollect that state to indicate an error
c
Does that mean you can do the following then:
```cloudshift.goLang {
}```
…only if you also register an extension val on cloudshift for golang.
z
you can know that state and emit an error in compile time with a custom message indicating that
library
with
app
can’t work
Well it would mean compilation has to run and fail first. both are still on the compile classpath, and I would want to use generated extensions to avoid them
c
Copy code
public val CloudShiftExtension.golang: GoLangExtension
    get() = extensions.getByType()
…allows
cloudshift.golang
to work. separate from
Copy code
public fun CloudShiftExtension.golang(action: GoLangExtension.() -> Unit): Unit
which is the nested DSL.
v
One important question also is, whether you only want to support Kotlin DSL properly, or also Groovy DSL. Because in the latter case the option to make an own extension function is not really feasible.
z
only the kotlin DSL for me, all in
c
<whew>. Yes, all that only works in Kotlin.
v
What about if both plugins are applied, is that invalid or could that legally be?
z
@Vampire invalid
we have 3 convention plugins currently (android library, android app, java library) - only 1 of them will be applied at any time
v
What you could do, but you might not like it, is to have one method in your extension and then have
Copy code
config {
    whatever {
        // actual configuration here
    }
}
and then you can do your post-configure actions in the
whatever
function after executing the given action block and throw if the method is called a second time.
👍 1
z
Our current approach, with a
data class
which is nasty
Copy code
// setting `moduleConfig` executes our convention plugin configuration logic immediately
// and can only happen once per project
moduleConfig = me.Module(
    gradlePath = project.path,  // silly 
    convention = LIBRARY_JAVA, // silly - should know automatically the library convention based on the applied plugin
    isPublished = true, // silly, bc our app can't be published
)
I want to change it to:
Copy code
config {
  isPublished = true // field would not exist in an Android app project
}
yeah I guess I have to do some workaround here 😕
I think my best approach would be something like:
Copy code
moduleConfig = config {
 
}
and evaluate immediately once
moduleConfig
is set
v
Another fancy option:
Copy code
interface AppConfigValues {
    val setting: Property<String>
}
abstract class AppConfig {
    @get:Inject
    abstract val objects: ObjectFactory

    infix fun ig(action: Action<AppConfigValues>) {
        val setting = objects.newInstance<AppConfigValues>().also(action::execute).setting.get()
        println("configured $setting")
    }
}
project.extensions.create<AppConfig>("conf")
and then
Copy code
conf ig {
    setting.set("YAY!")
}
Or
Copy code
interface AppConfigValues {
    val setting: Property<String>
}
abstract class AppConfig {
    @get:Inject
    abstract val objects: ObjectFactory

    operator fun rem(action: Action<AppConfigValues>) {
        val setting = objects.newInstance<AppConfigValues>().also(action::execute).setting.get()
        println("configured $setting")
    }
}
project.extensions.create<AppConfig>("config")
and then
Copy code
config % {
    setting.set("YAY!")
}
Or any of the other overloadable binary operators: https://kotlinlang.org/docs/operator-overloading.html#binary-operations
Copy code
config .. {
    setting.set("YAY!")
}
or any other, just pick one
Just not
invoke
as the type-safe accessor with action argument will win then
Well, the one argument
invoke
that is. Two argument
invoke
would work too:
Copy code
interface AppConfigValues {
    val setting: Property<String>
}
abstract class AppConfig {
    @get:Inject
    abstract val objects: ObjectFactory

    operator fun invoke(nothing: Nothing?, action: Action<AppConfigValues>) {
        val setting = objects.newInstance<AppConfigValues>().also(action::execute).setting.get()
        println("configured $setting")
    }
}
project.extensions.create<AppConfig>("config")
and then
Copy code
config(null) {
    setting.set("YAY!")
}
z
haha those options are wild
side note, kind of a bummer that
Property<String>
has to have:
Copy code
setting.set("YAY!")
instead of:
Copy code
setting = "YAY!"
bc the latter is so much nicer. I wish kotlin could support both options somehow
v
Kotlin native support for assignment operator overloading is more or less rejected. But the Gradle guys are working on a compiler plugin to make the syntax possible.
❤️ 1
c
This magic snippet let’s you do `setting.finalValue = “YAY!”
Copy code
public var <T> Property<T>.finalValue: T
    get() = throw UnsupportedOperationException("Do not use property.value; call property.get() if that is what is intended, or use .from(property) to chain properties together")
    set(value) = value(value).finalizeValue()
Or this one if you don’t want to also finalize the value:
Copy code
public var <T> Property<T>.value: T
    get() = throw UnsupportedOperationException("Do not use property.value; call property.get() if that is what is intended, or use .from(property) to chain properties together")
    set(value) {
        value(value)
    }
z
@Chris Lee what’s the benefit of finalizing the value?
c
Prevents it from being changed later.
👍 1