2021-10-10

4: Defining and Using Classes in Gradle

<The previous article in this series | The table of contents of this series |

What does a class defined in a Gradle script become? An inner class in the script class, a local inner class in the 'run' method, or what else?

Topics


About: Gradle

The table of contents of this article


Starting Context



Target Context


  • The reader will know what does any class defined in Gradle become and how to define and use any class.

Orientation


There is an article that introduces a better way of writing scripts in Gradle.


Main Body

Stage Direction
Here is Special-Student-7 in a room in an old rather isolated house surrounded by some mountains in Japan.


1: Defining a Class in the Script


Special-Student-7-Hypothesizer
I can define a class in the script, like this.

@Gradle Source Code
class CustumTaskA extends DefaultTask {
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

A reason why I define my class is that I have to define a custom task class, as in the above code.

Another reason is that I want a utility class that is used in multiple tasks.


2: It Does Not Become Any Inner Class or Local Inner Class


Special-Student-7-Rebutter
Anyway, what does such a class become? I mean, where is the class placed in the automatically-generated 'org.gradle.groovy.scripts.BasicScript' sub class?

Special-Student-7-Hypothesizer
I guessed that it would become an inner class in the script class, or maybe a local inner class in the 'run' method, but that has turned out be not the case.

It becomes an outer class.

Special-Student-7-Rebutter
Um? What are the implications of the fact?

Special-Student-7-Hypothesizer
The outer class cannot directly access the script class fields, the script properties, or the project properties.

Special-Student-7-Rebutter
Well, it is rather inconvenient. Can we not define an inner class in the script class?

Special-Student-7-Hypothesizer
As far as I know, no.


3: Defining a Class Outside the Script File


Special-Student-7-Hypothesizer
As the class does not become any inner class of the script class, I do not rather want to write it in the script file, because the class does not become really a part of the script.

Groovy-wise, such a class can be put into another Groovy source file (with the file name extension of 'groovy'), but well, Gradle does not read such a Groovy source file or any 'gradle' extension file, as far as I know. . . . I wonder why Gradle does not use Groovy more straight-forwardly, than badly twisting its expected behavior.

Well, I can put the class in another Gradle file and "apply" the Gradle file into the script, but a problem is that the class is invisible in the script, as this gives an error, "unable to resolve class ClassB".

Classes.gradle

@Gradle Source Code
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

build.gradle

@Gradle Source Code
apply ("from": "Classes.gradle")

ClassB l_classB = new ClassB ()

Special-Student-7-Rebutter
Why?

Special-Student-7-Hypothesizer
The reason is that a different class loader is used for each Gradle file.

So, what should I do? . . . If a class instance has been put into, for example, a project property, the class instance can be accessed in the script, as this is OK.

Classes.gradle

@Gradle Source Code
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

ext.classB = new ClassB ()

build.gradle

@Gradle Source Code
classB.instanceMethodB ()

Special-Student-7-Rebutter
That is as expected.

Special-Student-7-Hypothesizer
But what if the class itself, not any class instance, has to be used in the script? For example, a custom task class has to be used in the script in order to create a task from the class.

In fact, I can put the class into a project property, as this is OK.

Classes.gradle

@Gradle Source Code
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

class CustumTaskA extends DefaultTask {
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

ext.ClassB = ClassB.class
ext.CustumTaskA = CustumTaskA.class

build.gradle

@Gradle Source Code
ClassB.staticMethodB ()

task ("custumTaskA", type: CustumTaskA, {
})

But still, I cannot create any instance from the class with the 'new' operator, in the script.

Although I have not bumped into such necessity, if do, I will be able to have a factory method, like this.

Classes.gradle

@Gradle Source Code
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public static ClassB createInstance () {
		return new ClassB ()
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

build.gradle

@Gradle Source Code
def l_classB = ClassB.createInstance ()
l_classB.instanceMethodB ()

I cannot specify the "l_classB" variable type as 'ClassB', though; a shame!


4: Injecting the Script Object or the Project Object into the Class


Special-Student-7-Hypothesizer
As I said, the inconvenience of being an outer class is that the class cannot access the script fields, the script properties, or the project properties, at least directly.

If the class needs to access some of them, they have to be made visible to the class instance somehow.

I usually inject the project object into the class instance.

If the class is something I instantiate with the 'new' operator, I can pass the project object as a constructor argument, like this.

Classes.gradle

@Gradle Source Code
class ClassB {
	private Project i_project
	
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public static ClassB createInstance (Project a_project) {
		return new ClassB (a_project)
	}
	
	public ClassB (Project a_project) {
		i_project = a_project
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

If the class is a task class, which is instantiated by the 'task' method, I can set the project object in the configuration closure, like this.

Classes.gradle

@Gradle Source Code
class CustumTaskA extends DefaultTask {
	protected Project i_project
	
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

ext.CustumTaskA = CustumTaskA.class

build.gradle

@Gradle Source Code
task ("custumTaskA", type: CustumTaskA, {
	i_project = project
})


5: A Project Extension: a Strategy I Employ


Special-Student-7-Hypothesizer
A strategy I employ is to have a project extension.

Special-Student-7-Rebutter
What is that?

Special-Student-7-Hypothesizer
Project extension is a project property that is an instance of a class that has some fields and some methods that are used commonly in tasks.

For example, I define a project extension class and set its instance as a project property, like this.

Classes.gradle

@Gradle Source Code
@groovy.transform.CompileStatic
class ProjectExtension {
	private Project i_project
	public String i_parameterA
	public String i_parameterB
	
	public ProjectExtension (Project a_project) {
		i_project = a_project
		i_parameterA = "parameter A"
		i_parameterB = "parameter B"
	}
	
	public void methodA () {
		println ("### methoidA!")
	}
	
	public void methodB () {
		println ("### methoidB!")
	}
}

ext.extension = new ProjectExtension (project)

Why do I not set such data and functions directly as project properties, like this?

build.gradle

@Gradle Source Code
ext.i_parameterA = "parameter A"
ext.i_parameterB = "parameter B"
ext.methodA = {
	println ("### methoidA!")
}
ext.methodB = {
	println ("### methoidB!")
}

The reason is that such functions are closures, and closures are certainly handy, but cannot be staticly compiled, being a performance liability.

Why does the class instance have to be set as a project property?

Well, it does not particularly have to, but as the project object is meant to be injected around into class instances and of course is visible in the script, the class instance will become visible anywhere, that way.


References


<The previous article in this series | The table of contents of this series |