Project.description vs. project.ext.description

Assigning to project.ext.x works generally, and works great for internally managed properties like ‘title’, but the project ‘description’ property is very special in that assigning to project.ext.description has no effect and reading project.description still returns null.

I have a plugin that handles property settings generally. It tests if the parent object (‘project’ in this case) has an ‘ext’ member that is assignable from ExtraPropertiesExtension and assigns with parentObject.ext.x if so. So far that seems to work for everything other than this special ‘description’ property. Is there a better test I can do to check if a property should be set with parentObject.x instead of with parentObject.ext.x?

Following build file shows that only assigning directly to ‘project.description’ effects reading of ‘project.description’.

defaultTasks ‘echoDescription’

task echoDescription << {

project.ext.description=‘Coded val1’

println 'description set? ’ + project.hasProperty(‘description’)

println ‘description=(’ + project.description + ‘)’

project.ext.set(‘description’, ‘Coded val2’)

println 'description set? ’ + project.hasProperty(‘description’)

println ‘description=(’ + project.description + ‘)’

project.setProperty(‘description’, ‘Coded val3’)

println 'description set? ’ + project.hasProperty(‘description’)

println ‘description=(’ + project.description + ‘)’

}

task echoOther << {

project.ext.other=‘Coded val’

println 'other set? ’ + project.hasProperty(‘other’)

println ‘other=(’ + project.other + ‘)’

}

task echoTitle << {

project.ext.title=‘Coded val’

println 'title set? ’ + project.hasProperty(‘title’)

println ‘title=(’ + project.title + ‘)’

}

Using ‘project.hasProperty(‘description’)’ is the best you can do right now.

Looking at this, I’m wondering whether the ‘ext’ object should inherit from its owner. This would mean that setting a property via ‘ext’ conceptually changes from:

“If the owning object does not have this property, add it with this value”

to:

“Set the value of this property on the owning object, creating it if necessary”

I’ve raised GRADLE-2536 for this.

As the code I provided shows, using “project.hasProperty(‘description’)” does not work. That always returns true for ‘description’ (execute default task of provided build file). I.e. the test generalObject.hasProperty(‘name’) won’t work for me because where ‘generalObject’ happens to be a Project and ‘name’ happens to be description, the test is useless.

It returns true because the project does have this property.

The ‘hasProperty()’ method checks for the existence of a property, not that it has a value.

I know. If it always returns the same exact value then it is useless regardless of what it is or isn’t testing, and that is the case with project.hasProperty(‘description’). Not that ‘hasProperty()’ is useless, but with object of ‘project’ and param of ‘description’, as was suggested, it is useless.

I’m not quite understanding the problem then.

Are you wanting a forward declaration of all the properties baked on to Project?

Run the targets in the supplied build file.

For property ‘other’ and ‘title’, project.ext.other and project.ext.title can be assigned to and can then be accessed via project.other and project.title (among other means).

As explained previously, project.ext.description can be assigned to but has no effect.

The difference in behavior is troublesome to my routines that set object properties in a general way. Is there some test I can make to tell if property p should be assigned to obj.p vs. obj.ext.p?

I did run the code.

My initial response covers the case you are describing. The problem you are seeing is that setting an “extra property” that the owner already has does not affect the owner’s property. It only affects the owner if the owner does not have the property. My proposal is to change this so that the extra property assignment forwards to the owner if the owner already has the property you are setting.

The solution in the meantime is to set conditionally based on ‘hasProperty()’, not blindly set the extra property.

project.hasProperty("description") ? project.description = foo : project.ext.description = foo

As written, that code is useless because project.hasProperty(“description”) is always true. Would be more efficient to just write:

project.description = foo

If you mean to suggest to use the obj.hasProperty(“name”) test generally, that is useless too because it evaluates to true whether the object is set by an “extra” property or by the owner’s direct property. It ALWAYS returns true, so why execute the test with those values (‘project’ and ‘description’)?

I know that you are a very bright guy Luke, seriously. It’s likely that you mean something else that I’m not catching. Could you provide a code snippet that could toggle in some way that is useful?

We’ve definitely got some cross communication going on here, so let’s back up. This is likely my fault for not reading deeply enough.

So, assuming that this is the stated problem:

Is there a better test I can do to check if a property should be set with parentObject.x instead of with parentObject.ext.x?

There is no better test than:

project.hasProperty("description") ? project.description = foo : project.ext.description = foo

Now, that’s pretty redundant because we know project.description exists.

In a dynamic situation where we are less certain of what we are setting, we’d do something like this:

[description: "foo", title: "bar", other: "baz"].each { key, value ->
  project.hasProperty(key) ? project."$key" = value : project.ext."$key" = value
}

Does that answer it?

(btw, thanks for the compliment)

Your code does not distinguish between cases where a property has previously been set directly on the object vs. on the extension object. Yes, project.propName will work correctly afterward, but the code sets direct object properties where it shouldn’t need to and could mess up introspective code or cause real problems in the future when Gradle really cracks down on setting object properties directly.

If I run project.ext.other = ‘Orig val’ first and then run my setter method, I do not want to set an overriding direct object property project.other (which I believe would be done by your ‘project."$key" = value’), but rather change the value of the existing obj.ext.other. Setting the direct object property as in your code would avoid the visible warnings but still sets a direct object property where the Gradle docs exhort to use extension properties.

Yes, project.propName will work correctly afterward, but the code sets direct object properties where it shouldn’t need to and could mess up introspective code or cause real problems in the future when Gradle really cracks down on setting object properties directly.

I don’t know what you mean by “shouldn’t need to”, “mess up introspective code or cause real problems” or “really cracks down on setting object properties directly”.

If I run project.ext.other = ‘Orig val’ first and then run my setter method, I do not want to set an overriding direct object property project.other (which I believe would be done by your ‘project.“$key” = value’), but rather change the value of the existing obj.ext.other.

If a property exists on an object because it was added as an extra property, setting that property on the owner changes the extra property.

assert !project.hasProperty("custom")
assert !project.ext.has("custom")
  project.ext.custom = "foo"
assert project.custom == "foo"
project.custom = "bar"
assert project.custom == "bar"
assert project.ext.custom == "bar"

Setting the direct object property as in your code would avoid the visible warnings but still sets a direct object property where the Gradle docs exhort to use extension properties.

The Gradle docs are intended to convey that you need to use the ‘ext.’ space to declare properties. Once they are declared, getting/setting them via the owner is perfectly fine.

Oh, ok. I’m all set then. Thanks for you patience, Luke.

Glad we sync’ed up in the end. No problem.

Happy Gradling.