This message was deleted.
# community-support
s
This message was deleted.
j
Plugins order shouldn't matter, if it is, then something is done wrong.
AFAIK you can't extend a third party extension, as an alternative you can configure it via your extension and your plugin, and avoiding using the original one
v
The thing you are after for your environments is a NamedDomainObjectContainer. Yes, you can extend other extensions and that should also produce type-safe accessors for Kotlin DSL. Any object created through Gradle means is automatically
ExtensionAware
even if it does not declare it, so if the extension in question does not declare it, just cast it to
ExtensionAware
. Regarding order Javi already said it, plugins should not depend on application order or something is done badly. Instead either apply the plugin you need in your plugin if it must always be there, or react to the other plugin being applied using
pluginManager.withPlugin("...") { ... }
j
Is there any sample about extending a third party extension?
v
For sure, but none I know of. 😄 But it is not different than extending your own extension. Just apply the plugin or react to it being applied, get the extension, cast it to
ExtensionAware
if it does not declare it, add the extension like you would also to the project directly.
j
I am missing a piece, after casting it to
ExtensionAware
, how can I add a new property to it? Would it be accessed under the same name? For example, if the plugin declares
FooExtension { val foo: Property<String> }
as
foo
. How can I add something after using
val foo = extensions.getByType<FooExtension>() as ExtensionAware
?
Found on the docs of
ExtensionAware
Copy code
// Extensions are just plain objects, there is no interface/type
class MyExtension {
  String foo

  MyExtension(String foo) {
    this.foo = foo
  }
}

// Add new extensions via the extension container
project.extensions.create('custom', MyExtension, "bar")
//                       («name»,   «type»,       «constructor args», …)

// extensions appear as properties on the target object by the given name
assert project.custom instanceof MyExtension
assert project.custom.foo == "bar"

// also via a namespace method
project.custom {
  assert foo == "bar"
  foo = "other"
}
assert project.custom.foo == "other"

// Extensions added with the extension container's create method are themselves extensible
assert project.custom instanceof ExtensionAware
project.custom.extensions.create("nested", MyExtension, "baz")
assert project.custom.nested.foo == "baz"

// All extension aware objects have a special "ext" extension of type ExtraPropertiesExtension
assert project.hasProperty("myProperty") == false
project.ext.myProperty = "myValue"

// Properties added to the "ext" extension are promoted to the owning object
assert project.myProperty == "myValue"
d
Thanks I could add the extension
Copy code
interface EnvironmentPluginExtension {
    val flavorName: Property<String>
}
and inside my plugin:
Copy code
val extension = extensions.getByType<ApplicationExtension>()
            val envExtension = (extension as ExtensionAware).extensions.create<EnvironmentPluginExtension>("environments", EnvironmentPluginExtension::class.java)
            envExtension.flavorName.convention("environment")
I now have
environments
inside
android
BUT from the
build.gradle.kts
script I need to do
Copy code
android {
  environments {
    flavorName.set("test")
  }
}
instead of
flavorName = "test"
and inside the plugin if I try to read it:
Copy code
envExtension.flavorName.get()
I get the "convention":
environment
do I have to use
doLast
?
I don't have a task
j
That is normal,
Property<*>
is the way to configure things, and it is used with
set
, in the future it will be syntax sugar so you can set properties via
=
The real problem for me, is the default Android APIs are not using
Property
, only on
onVariants
and so on lambdas if I remember correctly.
d
Other plugins, like the android plugin, let me use
=
tho'
j
if you want to config the Android extension with your
Property<*>
, and you want to avoid ordering issues due using
get()
in the configuration phase, you would need to use variants lambdas Check this: https://developer.android.com/build/extend-agp
That is only happening in some properties but because they are not
Property
, but if you check those variants lambdas you would see that they use properties so you can use something like
foo.set(bar)
, without calling
bar.get()
Or in other words, if you want to extend AGP, you shouldn't need to interact with other APIs than variants lambdas
v
I now have
environments
inside
android
BUT
from the
build.gradle.kts
script I need to do
As I said, you want a
NamedDomainObjectContainer
d
As I said, you want a
NamedDomainObjectContainer
Yes I got that, I'm going through steps, starting simple with a string property to see how i can write it and read it and at present I'm not able to read it
v
Because you immediately read it using
get()
before the applying project had a chance to configure it. Don't do that with the lazy properties, but use them lazy, that's all their purpose.
d
yep I figured that much, but how am I supposed to read the property for use in my plugin? I do not have a task, so I do not have a doLast like in the example
j
otherPluginExtension.someProperty.set(yourProperty)
the other plugin will use the property in a task
d
I cannot do that. the properties I set in there I have logic to remap on the android plugin that doesn't map 1:1 with the property
j
otherPluginExtension.someProperty.set(yourProperty.map { ... })
Copy code
otherPluginExtension.someProperty.set(
    provider {
        computeValue(propertyOne.get(), propertyTwo.get())
    }
)
v
Then don't make it a property, but make a function in your extension that the consumer calls to which you can then react and do things
Btw. for you original question(s):
Copy code
interface Environment : Named {
    val propertiesFile: RegularFileProperty
    val disableReleaseBuild: Property<Boolean>
}

pluginManager.withPlugin("idea") {
    val idea = project.extensions.getByType<IdeaModel>() as ExtensionAware
    val environments = objects.domainObjectContainer(Environment::class)
    idea.extensions.add("environments", environments)
    environments.whenObjectAdded {
        // ...
    }
    // or
    environments.configureEach {
        // ...
    }
    // or whatever
}
allows you to do in the consumer
Copy code
idea {
    environments {
        create("dev") {
            propertiesFile.set(file("dev.properties"))
            disableReleaseBuild.set(true)
        }
    }
}
d
Right now I have no configuration what I do is 1. enable buildConfig feature in android plugin 2. I create a new flavor dimension with a name "environment" 3. I create 3 new flavor for the for the dimension "environment": dev, stage, prod 4. each flavor go look for a file in the project folder named
environment.$env.properties
, parse it and set
buildConfig
for each property I find in there Instead of doing this automatically I'd like to give configurability for • enable the buildConfig feature only if there are environment configured • the name of the flavor dimension (default to environment) • which environment to use, which file to look for for each environment (with default again, if not specified the file name is the one above)
v
As I said, you probably want to have an extension with a function that does the configuration you want and then the consumer can call that.
Something like this:
Copy code
abstract class Environment @Inject constructor(val name: String) {
    var propertiesFile: File? = null
    var disableReleaseBuild: Boolean? = null
}

abstract class EnvironmentsExtension {
    @get:Inject
    abstract val objects: ObjectFactory

    fun create(name: String, configuration: Action<Environment>) {
        val environment = objects.newInstance<Environment>(name)
            .also(configuration::execute)
        // ...
    }
}

pluginManager.withPlugin("idea") {
    val idea = project.extensions.getByType<IdeaModel>() as ExtensionAware
    idea.extensions.create("environments", EnvironmentsExtension::class)
}
and then
Copy code
idea {
    environments {
        create("dev") {
            propertiesFile = file("dev.properties")
            disableReleaseBuild = true
        }
    }
}
d
(I'll be back after some work calls)
👋 1
Ok I've read both your messages @Vampire awesome, I think I have everything I need to get cracking (I just cannot do it right away because I spent too much on this and forgot I had another thing to finish eheh)
👌 1
I'll resurrect this thread for followup questions or just for the well deserved thanks if no questions 😄
👌 2
Thank you @Vampire I’ve implemented the
Named
one. Works great. But I also want to explore the second with the Action. And understand both better. I think I need to read https://docs.gradle.org/current/userguide/custom_gradle_types.html and you previously pointed me to https://docs.gradle.org/current/userguide/custom_gradle_types.html I’ve looked at the code for the Named interface but I don’t understand how all that is generated automatically hopefully those docs will help me out figure it out. Does the injection part works out of the box without any dependency?
v
If an object is generated through Gradle means (
objects.newInstance
,
extensions.create
, ...) Gradle automatically decorates the class to make it
ExtensionAware
(even if that interface is not declared, hence you can cast almost all objects to it and use it), to implement
Named
if declared and not done manually, to implement `@Inject`ed services, to implement abstract `Property`s and so on. That way you can save quite some boilerplate code by just let Gradle do its magic.