Gradle: Using string interpolation in model DSL - gradle

How can I substitute a project property in a string in a model DSL? I tried the following:
apply plugin: 'com.android.model.native'
model {
android {
...
sources {
main {
jni {
source {
srcDirs "src"
include "*.cpp"
}
exportedHeaders {
srcDir "${project.rootDir}/include"
}
}
}
}
}
}
But I got this error:
Error:Attempt to read a write only view of model of type 'org.gradle.model.ModelMap<org.gradle.language.base.FunctionalSourceSet>' given to rule 'android { ... } # android/build.gradle line 6, column 5'
It works if I assign the property value to a variable outside of the model block and substitute that variable instead:
def fooDir = project.rootDir
...
srcDir "${fooDir}/include"
But that's a bit inconvenient.

It's a weird error, but it means you are trying to access a property that has not been declared as an input for the rule you are creating. To declare something as an input you have to use the "special syntax" for the model dsl. For example, to access the project.buildDir, you must use:
$.buildDir
Unfortunately, the project.rootDir is not bound to a model path yet. Caching the value in a variable accessible to your model rules (as you have done) seems to be a decent workaround for now.

Related

populate NamedDomainObjectContainer from file

Suppose I am currently authoring a Gradle plugin, and I have this extension:
abstract class MyExtension {
abstract val inputFiles: ConfigurableFileCollection
val names: Provider<List<String>> by lazy {
// Read input files to get a list of names
inputFiles.elements.map { ... }
}
abstract val instances: NamedDomainObjectContainer<MyType>
}
What I want to do in my plugin is to make sure that every name provided by names is registered in instances if not already registered. That is, something like this:
names.get().forEach {
if (!instances.names.contains(it)) {
instances.register(it) {
// Additional configuration goes here
}
}
}
I have heard that project.afterEvaluate() is an option, however I have also heard that it is a bad option that shouldn't be used. What would be a way to have this functionality?
You can use another DomainObjectContainer, which has various functionality to allow it to stay up-to-date with other objects being configured.
For instance you could have the extension class:
abstract class MyExtension {
abstract val inputFiles: ConfigurableFileCollection
abstract val names: DomainObjectSet<String>
abstract val instances: NamedDomainObjectContainer<MyType>
}
Then have in your plugin code:
val ext = project.extensions.create("MyExtension", MyExtension::class.java)
ext.names.all { eachName ->
ext.instances.register(eachName) {
// Additional config
}
}
ext.names.addAllLater(ext.inputFiles.elements.map { ... })
The lambda passed to all will be executed for each new name added to names, including all the files, so all the collections will stay current with each other.

In Gradle, how do you perform validation of lazily evaluated properties (on extensions)?

Is there a way to validate a property value when the property is evaluated? I can't do it in the getter because that returns the Property object - I want the validation to run only when the actual value is calculated (i.e. I want to be lazy evaluation friendly).
They show extensions using the Property object here:
https://docs.gradle.org/current/userguide/lazy_configuration.html#connecting_properties_together
However, they don't explain how to do property validation when the value is calculated. Here is the snipet of code from the Gradle documentation provided example:
// A project extension
class MessageExtension {
// A configurable greeting
final Property<String> greeting
#javax.inject.Inject
MessageExtension(ObjectFactory objects) {
greeting = objects.property(String)
}
}
If I wanted to make sure the value of greeting was not equal to test, then how would I do that when it is evaluated?
For most use cases, it should be sufficient to just validate the property value once you resolve it in your task or in other internal parts of your plugin. Only a few extensions are actually designed to be consumed by other plugins or the build script.
Gradle does not provide some validation that can be attached to a property, however you can build this functionality on your own like in the example below:
class MessageExtension {
private final Property<String> _greeting
final Provider<String> greeting
#javax.inject.Inject
MessageExtension(ObjectFactory objects) {
_greeting = objects.property(String)
greeting = _greeting.map { value ->
if (value.equals('test'))
throw new RuntimeException('Invalid greeting')
return value
}
}
def setGreeting(String value) {
_greeting.set(value)
}
def setGreeting(Provider<String> value) {
_greeting.set(value)
}
}
project.extensions.create('message', MessageExtension)
message {
greeting = 'test'
}
println message.greeting.get()
I turned the Property into a backing field for a Provider that runs the validation when resolved. If you do not want to throw an exception, but just return an empty Provider, you may replace the map with a flatMap.

How do I access nested configuration values from my custom gradle extension?

I'm writing a custom gradle plugin that needs to accept an arbitrary number of nested parameters from the buildscript. Something like:
myPlugin{
configObjects = [
{
name="objectA",
value=5,
},
{
name="objectB",
value=9,
}
]
}
...where the number of items in configObjects, and the the values inside them is defined in whatever buildscript is importing the plugin.
So in my plugin code, I create an extension...
val config = extensions.create("myPlugin", myPluginTaskConfiguration::class.java, project)
tasks {
register<myPluginTask>("myPlugin") {
configObjects= config.configObjects
}
}
and a class defining the structure of the data received through the extension:
open class myPluginTaskConfiguration(project: Project) {
#Input
#Option(option="configObjects", description = "list of configObjects")
var configObjects:List<ConfigObject>?=null
}
Gradle allows me to specify the outer type, but apparently not the inner members. Running my plugin task I get the following error:
class build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 cannot be cast to class com.myplugin.ConfigObject (build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 is in unnamed module of loader org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler$ScriptClassLoader #224ed88; com.myplugin.ConfigObject is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader #72fe231e)
It's not clear to me what the type of the objects in the configObjects block is (well, apparently they're of type build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5, but I don't think that's something I can use at author-time)
How can I take the list of items from my groovy buildscript, and convert them into typed objects in my plugin (preferably in a way that allows the IDE to provide suggestions/hints to users editing the buildscript)?
#Input and #Option are for tasks. From the looks of it, you are using them for extensions.
There is no need to need to pass in a project instance in the constructor of a Task. All Tasks have a reference to the Project they belong to https://docs.gradle.org/current/javadoc/org/gradle/api/Task.html#getProject--
With that said, full working example in Kotlin would be:
open class MyPluginTaskConfiguration #Inject constructor(objects: ObjectFactory) {
val configObjects: ListProperty<Map<*, *>> = objects.listProperty()
}
open class MyPluginTask : DefaultTask() {
#Input
#Option(option="configObjects", description = "list of configObjects")
val configObjects: ListProperty<Map<*, *>> = project.objects.listProperty()
#TaskAction
fun printMessage() {
configObjects.get().forEach {
println("$it")
}
}
}
val config = extensions.create("myPlugin", MyPluginTaskConfiguration::class.java)
configure<MyPluginTaskConfiguration> {
configObjects.set(listOf(
mapOf<String, Any>(
"name" to "objectA",
"value" to 5
),
mapOf<String, Any>(
"name" to "objectB",
"value" to 9
)
))
}
tasks.register("myPlugin", MyPluginTask::class) {
configObjects.set(config.configObjects)
}
Executing the above produces:
./gradlew myPlugin
> Task :myPlugin
{name=objectA, value=5}
{name=objectB, value=9}
Refer to below doc for more details:
https://docs.gradle.org/current/userguide/lazy_configuration.html

Relay: Parametrize getFragment based on results of a parent query

My CMS returns me a list of nodes each having its own nodetype. For each nodetype I have a corresponding GraphQL type defined.
type getContent {
content: [ContentNode]
}
I want a query like:
{
content{
contentType
properties {
${ContentType.getFragment('content', type: $contentType???)}
}
}
}
ContentType would return a correct fragment definition based on type variable provided to it. But how do I get $contentType from parent results?
You can't have fragments that depend on actual value of the parent, because fragments are composed before the query request is actually made to server. There are two different ways to handle this, one is to have fragment vary based on variables and other is to use interface and typed fragments inside your component.
Here is a good answer showing example of using variables: Conditional fragments or embedded root-containers when using Relay with React-Native
For the interface solution, if you have ContentNode an interfaces with implementations like 'ContentNode1' and 'ContentNode2', then you can do something like that:
{
content {
${ContentType.getFragment('content')}
}
}
And in your ContentType component
fragment on ContentNode {
contentType
someOtherCommonField
... on ContentNode1 {
someContent1Field
}
... on ContentNode2 {
someContent2Field
}
}

How can i place validations for fields in groovy for specific format

I have Domain class and in that for a particular field of type String, it accepts alphanumeric values,i need a validation in the format it should accept only AB12-QW-1 (or) XY-12 values. how can i validate the field.
Please suggest the solution.
Thanks.
Assume your domain class looks like this
class Foo {
String bar
}
If you can define a regex that matches only the legal values, you can apply the constraint using:
class Foo {
String bar
constraints = {
bar(matches:"PUT-YOUR-REGEX-HERE")
}
}
Alternatively, if you can easily list all the legal values, you can use:
class Foo {
String bar
constraints = {
bar(inList:['AB12-QW-1', 'XY-12'])
}
}
If neither of these solutions will work, then you'll likely need to write a custom validator method
You could use a custom validator:
class YourDomain {
String code
static constraints = {
code( validator: {
if( !( it in [ 'AB12-QW-1', 'XY-12' ] ) ) return ['invalid.code']
})
}
}
However, your explanation of what codes are valid is a bit vague, so you probably want something else in place of the in call
[edit]
Assuming your two strings just showed placeholders for letters or numbers, the following regexp validator should work:
constraints = {
code( matches:'[A-Z]{2}[0-9]{2}-[A-Z]{2}-[0-9]|[A-Z]{2}-[0-9]{2}' )
}
And that will return the error yourDomain.code.matches.invalid if it fails

Resources