This message was deleted.
# community-support
s
This message was deleted.
g
I tried something like that before and found that `all`/`whenObjectAdded` callbacks are executed with unconfigured object when it's added but before configure action from `create`/`register` is executed. You could overcome it by extending container and overriding one or two methods but it's a bit conterintuitive. Another approach is to provide your own method on extension which will add to the container and execute provided configuration callback and only then execute your code to consume configured object in the container. It seems the most robust approach or so it seems.
e
like this?
Copy code
fun add(name: String, config: Action<MyType>) {
    container.add(objects.newInstance(MyType::class, name).apply { config() })
}
will depend on the container… but it could work
g
I'm not sure if it's exact issue that you have but callback stacking is thing-in-itself
I could try to find my code tomorrow if you're interested, I'm half asleep right now =(
t
I'm definitely interested in finding the most intuitive and maintainable solution to this problem! Thank you. I find this Gradle behavior very weird
the trick of using the object factory, configuring the object, and then adding it to the container, works
l
Assuming
doThing()
is a piece of default configuration logic - my go to solution is using properties all the way through in my custom extensions. This allows for lazily providing configuration values based on other extension properties. Something like:
Copy code
open class MyType (private val theName, objects: ObjectFactory ) : Named {

  val isTrue = objects.property<Boolean>().convention(false)
  val theThing = objects.property<String>().convention(isTrue.map { if (it) ... })
}
However, properties can be a bit weird to handle for plugin users, as you can't simply use the
isTrue = false
syntax. Because of this I tend to have the property as an internal which is then augmented by custom get/set to provide the interface for plugin users. Like
Copy code
open class MyType (private val theName, objects: ObjectFactory ) : Named {

  var isTrue: Boolean
        get() = _isTrue.get()
        set(value) = _isTrue.set(value)
  internal val _isTrue = objects.property<Boolean>().convention(false)
  ...
}
d
There was an amazing explanation posted a while back, see [0] and [1]
t
the issue is that
doThing()
registers a task. So if
ifTrue
is false, no tasks get registered. If true, they're registered. So I can't just keep pushing things down with properties
l
Is registering the task in any case and running it only if the extension property is true an option? If not, then indeed
afterEvaluate
seems the most logical/understandable place to me...
e
yeah, registering all with a task and then using
onlyIf
to run it only when a property is true would seem to be the sanest solution to me
t
I think avoiding registering a task that won't be used is a reasonable goal
👍 1
e
reasonable goal it may be, but it's not achievable cleanly (IMO)
t
this seems clean enough to me
Copy code
val c = objects.newInstance(MyType::class.java, name).apply {
    config.execute(this)
}
container.add(c)
🤷
e
you don't know that the collection uses the same instantiator, and it violates the expectation that
container.create("foo") { container["foo"]!! }
exists. although perhaps neither is important to you
t
I agree it's a fair point. This is a small project, though, and we can test to verify the functional requirements. I'd rather Gradle create an option to let us say "run the creation config immediately, that's why I used
create
_after all_", but who knows when or if that will ever happen
I also wonder if I could do this somehow with a custom factory, and there is already an API for that, but it seems even more cumbersome / less maintainable
g
I looked through my code and that version was not committed, so no link to repo. Basically
create(String,Action)
and
register(String,Action)
have different behavior for observer using `whenObjectAdded`/`all`. •
create
instantiates object, adds to collection (and `all`/`whenObjectAdded` callbacks are called) and only then executes
Action
passed to callback. •
register
does opposite when realized, it instantiates object, executes passed
Action
(possibly decorated by
CollectionCallbackActionDecorator
which isn't applied to
create
AFAIK) and only then it is passed to `all`/`whenObjectAdded`. So I just override
create(String,Action)
when extend
AbstractNamedDomainObjectContainer
with one that has
add
and
configureAction.execute(object)
swapped. Original funtion: https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/java/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java#L79-L80
t
ah, create/register differentiation strike again. How bizarre
g
yeah, I was quite surprised when found it
l
I never liked that combination of ordering for
create
on containers. I agree that it would be more natural to have the creation configuration to run first before adding to the container. However it has been like that for ages. But learning that
register
is different is concerning. I’ll raise that internally once more.
thank you 2
g
But to change behaviour in register too would be a breaking change. Idea about strict mode looks better with each version)
t
I'd prefer they change
create()
. Maybe we can get that for Gradle 8 🙏
1
g
Same here
l
Changing is really unlikely given the history. However pushing forward to deprecate then remove
create
and keep the behavior of
register
sounds like a good plan.
e
if you're going to make your own container, wouldn't it be more consistent to override
create(name, action) = register(name, action).get()
?
t
in this context, if I used
register
, would
all
still cause all elements of the container to be realized? In the current situation, I need those elements available during project configuration
g
all
should realize all objects in the container, yeah. When I wanted to create tasks lazily based on items in the custom container I just registered tasks based on object names and used actual object from the container on demand in configureAction.
👍 1
e
Copy code
// build.gradle.kts
abstract class Foo : Named
val foos = container(Foo::class)
foos.all { println("foos.all($name)") }
foos.register("bar") { println("foos.register($name)") }
prints
Copy code
# ./gradlew
foos.all(bar)
foos.register(bar)
for me (Gradle 7.4.2)
g
Yeah, name added anyway. If you add some property to
Foo
and set it in register's configureAction vs same with create difference should become apparent
l
So @ephemient is right, there is no difference. Actions registered on the container with
all
are executed before the configuration action for both
create
and
register
. @grossws Would you be able to share a reproducer of what you did to see a difference? With the following in a
java
project:
Copy code
configurations.all {
    println("Configuration ${this.name} can be resolved ${this.isCanBeResolved()}")
}

configurations.register("testFoo") {
    setCanBeResolved(false)
}
it prints
Configuration testFoo can be resolved true
Changing the
all
to
configureEach
results in the same output. Moving the action to after the
register
changes the output since it is happening after.
g
@Louis Jacomet, I'll try to find where I got bitten by it and create a reproducer.
👍 1