I don't understand why we don't need to add quotes to the name of gradle task when we declare it
like:
task hello (type : DefaultTask) {
}
I've tried in a groovy project and found that it's illegal, how gradle makes it works.
And I don't understand the expression above neither, why we can add (type : DefaultTask), how can we analyze it with groovy grammar?
As an example in a GroovyConsole runnable form, you can define a bit of code thusly:
// Set the base class for our DSL
#BaseScript(MyDSL)
import groovy.transform.BaseScript
// Something to deal with people
class Person {
String name
Closure method
String toString() { "$name" }
Person(String name, Closure cl) {
this.name = name
this.method = cl
this.method.delegate = this
}
def greet(String greeting) {
println "$greeting $name"
}
}
// and our base DSL class
abstract class MyDSL extends Script {
def methodMissing(String name, args) {
return new Person(name, args[0])
}
def person(Person p) {
p.method(p)
}
}
// Then our actual script
person tim {
greet 'Hello'
}
So when the script at the bottom is executed, it prints Hello tim to stdout
But David's answer is the correct one, this is just for example
See also here in the documentation for Groovy
A Gradle build script is a Groovy DSL application. By careful use of "methodMissing" and "propertyMissing" methods, all magic is possible.
I don't remember the exact mechanism around "task ". I think this was asked in the Gradle forum (probably more than once).
Here is the code that make the magic possible and legal.
// DSL class - Gradle Task
class Task {
def name;
}
// DSL class - Gradle Project
class Project {
List<Task> tasks = [];
def methodMissing(String name, def args) {
if(name == "task"){
Task t = new Task(name:args[0])
tasks << t
}
}
def propertyMissing(String name) {
name
}
}
// gradle build script
def buildScript = {
task myTask
println tasks[0].name
}
buildScript.delegate = new Project()
// calling the script will print out "myTask"
buildScript()
Related
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.
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.
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
I'm trying to run stages from 2 class instances with parallel but i'm getting this error: "roovy.lang.MissingFieldException: No such field" from one of them, but if i'm running one of them it ok.
the groovy classes are in src folder and i'm using them with library and creating an instance for each of them.
this is my parallel code from the jenkins file:
def parallelStagesMap = pipelineDailyStages.collectEntries {
def name = it.getSetupname()
echo "name: " + name
["${name}" : it.generateStage(name)]
}
these are the classes:
package stages
class DSmall extends DStages implements Serializable{
def local_mx
def local_g
def local_agent
DamSmall(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_dcap_deploy, local_remotedebugport, local_m, local_g, local_agentoracle){
super(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_deploy, local_remotedebugport)
this.local_m = local_m
this.local_g = local_g
this.local_agent = local_agent
}
def generateStage(a){
return {
this.environment.stage("stage: ${a}") {
this.environment.echo "This is da."
}
}
}
}
class DaSmall extends DStages implements Serializable{
def local_m
def local_agent
def local_oracletemplate
def local_oracle_start_db
DasSmall(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_dcap_deploy, local_remotedebugport, local_mxs, local_agent, local_oracletemplate, local_oracle_start_db){
super(environment, local_setupname, local_rp_launch_id, local_ssbuild, local_catagry, local_runners, local_artifactstodownload, local_resourcepool, local_mngnetwork, local_datastore, local_vmfolder, local_deploy, local_remotedebugport)
this.local_mxs = local_mxs
this.local_agentoracle = local_agentoracle
this.local_oracletemplate = local_oracletemplate
this.local_oracle_start_db = local_oracle_start_db
}
def generateStage(a){
return {
this.environment.stage("stage: ${a}") {
this.environment.echo "This is da."
}
}
}
}
Finally I found a solution, I created the class instances from another groovy file that sits in vars folder(not from the jenkins file)
I have the simple Greeting example in xtext. So the DSL is defined like this:
grammar org.xtext.example.mydsl.Tests with org.eclipse.xtext.common.Terminals
generate tests "http://www.xtext.org/example/mydsl/Tests"
Model:
greetings+= Greeting*;
Greeting:
'Hello' name=ID '!';
Moreover, I have the following validator:
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
warning('Name should start with a capital',
TestsPackage.Literals.GREETING__NAME,
-1,
INVALID_NAME)
}
}
If I write the validator like this and have an expression like
"Hello world!"
in my model, the "world" is marked, i.e. there is this yellow line under it. What do I have to do if I want to mark only the keyword so in this case only the "Hello"?
I tried quite a few things and I can only manage to either mark the whole line "Hello world!" or just the "world".
Thank you!
have a look at the other methods for reporting a warning/error. there is one that takes an offset and length. you can use the node model to get them for the keyword
class MyDslValidator extends AbstractMyDslValidator {
public static val INVALID_NAME = 'invalidName'
#Inject extension MyDslGrammarAccess
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
val node = NodeModelUtils.findActualNodeFor(greeting)
for (n : node.asTreeIterable) {
val ge = n.grammarElement
if (ge instanceof Keyword && ge == greetingAccess.helloKeyword_0) {
messageAcceptor.acceptWarning(
'Name should start with a capital',
greeting,
n.offset,
n.length,
INVALID_NAME
)
}
}
}
}
}
I found another very simple solution I haven't thought of that includes changing the DSL a bit, i.e. add the keyword as a attribute.
Greeting:
keyword='Hello' name=ID '!';
Then the validator works as in the question:
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
warning('Name should start with a capital',
TestsPackage.Literals.GREETING__KEYWORD,
-1,
INVALID_NAME)
}
}