Deprecation of “dynamic properties”, and the new “extra properties”

I’d like to highlight one of the changes that was made late last week and is now available in the nightlies relating to “dynamic properties”. This is a rather far-reaching change so we’d like to get some feedback from our wonderful users before this feature is finalised for the next release.

Gradle domain objects are extensible in a number of ways. The most lightweight being that new properties can be added on demand. While this feature has proven to be very convenient for storing global settings and for inline custom tasks (among other things), it has also proven to be just a little too risky. With this mechanism, it is easy to create a new property on an object simply by misspelling the name of an existing property.

As an example, it can be all too easy to do this:

compileJava {
  classPath = configurations.compile
}

In this case we are trying to manually set the compile classpath when we compile our Java source. However, what we are really doing is creating a new property called ‘classPath’ and assigning the ‘compile’ configuration to it. This is going to have no effect whatsoever as the property that is used to specify the compile classpath is called “‘classpath’”. This can be a tricky and time consuming problem to track down.

For this and other reasons, we are deprecating what was known as “dynamic properties”. The new replacement, is what we are terming “extra properties”.

The two are not that different, except that creating extra properties requires a special namespace which makes it clear that you do not intend to set an existing property. Let’s take a look at how you might right a custom inline task now with dynamic properties:

task doStuff {
  prop1 = "foo"
  prop2 = "bar"
    doLast {
    println "$prop1 $prop2"
  }
}

As of the latest nightlies, this will now produce deprecation warnings when creating the dynamic properties ‘prop1’ and ‘prop2’. The same code using “extra properties” now looks like this:

task doStuff {
  ext.prop1 = "foo"
  ext.prop2 = "bar"
    doLast {
    println "$prop1 $prop2"
  }
}

Or…

task doStuff {
  ext {
    prop1 = "foo"
    prop2 = "bar"
  }
    doLast {
    println "$prop1 $prop2"
  }
}

All extra properties must be created through the “‘ext’” namespace. Once extra properties have been created, they are available on the owning object (in this case the ‘Task’) and can be read and changed. It’s only the initial declaration that needs to be done via the namespace.

It’s quite common to create dynamic properties on the project object.

version = "1.0-SNAPSHOT" // not a dynamic property, is project.version
isSnapshot = version.endsWith("-SNAPSHOT") // dynamic property
  if (isSnapshot) {
  // do snapshot stuff
}

With the new mechanism this looks like:

version = "1.0-SNAPSHOT"
ext.isSnapshot = version.endsWith("-SNAPSHOT")
  if (isSnapshot) {
  // do snapshot stuff
}

This change solves the problem of property misspelling, as for the next few releases a deprecation warning will be issued when trying to set a property that does not exist. Dynamic properties will eventually be removed entirely, meaning that this will be a fatal error in future versions of Gradle.

Another more subtle benefit is that user added properties are now clearly demarcated in the build configuration, making it easier to differentiate between added and provided functionality.

It’s also worth nothing that the “extra properties” mechanism is not to be used by plugins. That is, plugins should never populate the extra properties space as it is solely for “user” properties. Plugins should use the existing extension mechanism.

For more information and examples on the new “extra properties” mechanism, check out its documentation in the nightly builds.

If you have any thoughts or concerns you’d like to share about this change please leave replies below.

How does the update to dynamic properties apply to tasks?

I make use of a plugin that adds dynamic properties to the task that it creates. Making use of the plugin in my project with milestone-9 generates loads of unsightly deprecation warnings

I’d like to update the plugin and resolve the deprecation warnings but there are two issues:

  1. There doesn’t appear to be an ext property on a task. Adding the dynamic properties to the project doesn’t seem like the correct thing to do since they really are specific to the task and should scoped as such.

  2. How could I do this in a manner that is backwards compatible with previous versions of Gradle? Obviously there was not ext member on projects before milestone 9.

Why are the properties added dynamically?

The plugin in question is the RPM plugin. The properties are added dynamically because they are read from static members and enums from the Redline project that the RPM plugin depends on. You can see the code here if you’re interested: https://github.com/TrigonicSolutions/gradle-rpm-plugin/blob/master/src/main/groovy/com/trigonic/gradle/plugins/rpm/Rpm.groovy#L66

Tasks do have an ‘ext’ property when used from Groovy; ‘ext’ is just a regular Gradle extension (see the user guide if you are not familiar with this concept). To get access to an extension from Java, use ‘ExtensionContainer.getByName()’ (also see ‘ExtraPropertiesExtension’ in the DSL reference). Extra properties are mainly meant to be used from build scripts, not so much from plugins. To use extra properties from a plugin and at the same time stay backwards-compatible, you can check the Gradle version (or check for the existence of an ‘ExtraPropertiesExtension’ with ‘ExtensionContainer.getByName()’) and act accordingly.

Another thing to note is that dynamic/extra properties are only designed to be used by users for adhoc storage. Any plugin or reusable code should use an extension.

In this case, I’d strongly recommend using an extension for all of these extra properties.

The code base has Conventions and Extensions. It’s not clear when to use one versus the other. Can someone explain these two concepts in relationship to the removing of dynamic properties. Is it that Convensions add to Project, while Extensions add a namespace?

Is gradle transitioning from using Convention objects (e.g. MavenPluginConvention) to Extension objects (e.g. CheckstyleExtension)? Is project.getConvention().getPlugins().put(“maven”, mavenConvention) going away, in favor project.extensions.create(“checkstyle”, CheckstyleExtension)?

Always use extensions unless you can’t. And in that case, please tell us :slight_smile:

Conventions mix in to the target which has proven to be dangerous and not very user friendly. Extensions extend through a namespace making it clearer where the property/method is coming from.

Dynamic/extra properties are slightly different in that they are designed to be for very light ad-hoc additions. Plugin authors should not use them.

Yes, Gradle is transitioning away from convention objects. All new code uses extensions and as we work on older code we tend to deprecate any conventions and replace with extensions.

While I understand the rationale behind this change, I am not really happy with it. Dynamic properties as they were were one of the most beautiful things in Gradle when working with multiproject builds and subprojects.

For example, I have used them to allow authors of submodules of our project to provide a short description to be included in an installer, properties describing whether a module is required or optional, etc. by simply adding a header to the subproject’s build.gradle file that sets all the properties.

Now while I could probably do this using the extra properties concept, it is more complicated, does not integrate as well, and breaks builds for colleagues who are not so quick in updating to the latest Gradle version.

If such a concept is really needed at all (something I doubt, because why otherwise use a dynamic language like Groovy?), why not do it as in other (stricter) languages by adding a declaration block to a project where a user could declare the possible properties, like

properties {

myproperty1, myproperty2, myproperty3, … }

Then you could issue a warning in case a property is used that has not been declared.

Another complaint about the feature (addition to my previous complaint :wink: ):

I have currently used the dynamic property feature to declare my own Groovy methods in gradle scripts that I include using “apply from:”. Declaring methods in external scripts using e.g.

/**

  • Return the hostname of the build machine

*/ getHostInfo = { ->

return java.net.InetAddress.getLocalHost().getHostName(); }

is very natural, but issues a warning now (note that def getHostInfo() … does not work because of the way “apply from” works).

Of course I could also write my own plugin instead, but this would take away one of the advantages of Gradle over Maven … so -1 from my side for the feature. :frowning:

You just need:

ext.getHostInfo = { … }

In contrast, I found my biggest stumbling blocks when learning Gradle was all the conventions and dynamic properties. Figuring out where a property came from and then knowing if I got it correct was an absolute headache.

The extra properties do work like you propose, i.e. you can put dynamic properties in a block, e.g.:

ext { myproperty1, myproperty2, myproperty3 }

If you want checking of properties, “that has not been declared”, adding an extension, which will do this check isn’t too hard and I think you’ll find that every plugin does exactly this so that they get the behavior that you’re talking about. Here’s an example which you could drop into your code:

protected MyExtension createExtension() {
        extension = project.extensions.create('myext', MyExtension)
        extension.with {
            // Default for extension
            header = project.file("LICENSE")
            ignoreFailures = false
        }
        return extension
    }

Then it’s up to you to make a Groovy/Java class that backs the MyExtension. It would be available in this case as “project.myext.header” and settable as:

myext { myproperty1 = true }

Hi,

I’m wondering whether the same syntax should be used to migrate dynamic properties set on module dependencies, e.g.:

dependencies {

compile (group: ‘org.foo’, name: ‘bar’, version: ‘1.0.0’, bundleId: ‘org.foo.bar’, bundleVersion: ‘1.0.0.20120212’)

}

Would setting the additional attributes work when using e.g. ‘ext.bundleId’ ?

Regards,

Detelin

Good call. You’d have to do:

dependencies {
    compile (group: 'org.foo', name: 'bar', version: '1.0.0') {
    ext {
       bundleId = 'org.foo.bar'
      bundleVersion = '1.0.0.20120212'
    }
  }
}

We could add support for:

dependencies {
      compile (group: 'org.foo', name: 'bar', version: '1.0.0', "ext.bundleId": 'org.foo.bar', "ext.bundleVersion": '1.0.0.20120212')
  }

Having some more compact syntax to define those additional attributes would be great, as there may be many such dependencies. Also, while we are on the dependency definition topic, I noticed something strange in Gradle 1.0 milestone 9, it seems that the new ‘MapNotationParser’ is now removing the group/name/version keys, so the following:

List libraries = [

[ group: ‘commons-lang’, name: ‘commons-lang’ version: ‘2.6’ ],

[ group: ‘commons-collections’, name: ‘commons-collections’ version: ‘3.2.1’ ],

]

dependencies {

compile (libraries)

}

would cause all the Maps in the ‘libraries’ list to be empty, thus the list is not usable anymore for additional dependency definitions. This seemed to work with Gradle 1.0-m8a, is this change intentional?

Regards,

Detelin

Things tend to get lost when they are buried in threads with a different topic. Woul you mind creating a new post for the MapNotationParser issue please.