Can a groovy build script class acces the Gradle project directly? - gradle

Can a groovy class (located in buildSrc/src/main/groovy) access the project directly, or does the project have to be passed in explicitly?
I am able to access the project by explicitly passing it in as a method parameter, but I do not want to have to pass it in. For an example, I would like to be able to get access to the project via a static method call. Is this type of implicit access possible?
Explicit Access
import org.gradle.api.Project
class MyClazz {
static void foo(Project project) {
println project.version
}
}
Task in build.gradle
task foo() << {
MyClazz.foo(project)
}
Implicit Access via Static Method Call (this is the desired access pattern)
import org.gradle.api.Project
class MyClazz {
static void foo() {
println Project.getProject().version
}
}
Task in build.gradle
task foo() << {
MyClazz.foo()
}

You can use Groovy extension methods to do this.
here's a self-contained example, but should work with Gradle too:
class Project {
// we add this method dynamically
//static getProject() { [ version: 2.3 ] }
}
class MyClazz {
static void foo() {
println Project.getProject().version
}
}
class Gradle {
static def main(args) {
Project.metaClass.static.getProject = { [ version: 4.2 ] }
MyClazz.foo()
}
}

Related

Unable to resolve class - gradle

I have directory structure as below:
src
--foo
--A.groovy
--test
--groovy
buildTest.groovy
This is the A.groovy class :
package foo
class A {
void execute() {
println "Inside A"
}
}
This my buildTest.groovy
class buildTest {
#Test
void should_execute(){
A a = new A()
def result = a.execute()
}
}
When I run ./gradlew test, it throws error :
Unable to resolve class A
A a = new A()
^
This is my build.gradle :
plugins {
id "groovy"
}
repositories {
mavenCentral()
maven { url 'https://repo.jenkins-ci.org/releases/' }
}
dependencies {
compile 'org.eclipse.hudson:hudson-core:3.2.1'
implementation 'org.codehaus.groovy:groovy-all:3.0.7'
testImplementation 'junit:junit:4.12'
testImplementation "com.lesfurets:jenkins-pipeline-unit:1.3"
}
sourceSets {
main {
groovy {
srcDirs = ['src/foo']
}
}
}
Could someone help me in resolving this error.
You have declared a class A in package foo. (Note that the layout doesn't reflect this as the file should be in src/foo/foo/A.groovy, although Groovy doesn't really care).
However, the test class is not in a package and therefore can't see foo.A. Either import it or move the test class to package foo as well. Alternatively, remove the package declaration from class A, though I don't think that is good practice.
I also agree with the comment about not changing the layout unless you have a very good reason for it (e.g. you are working on a legacy project). This is probably what has caused confusion about what package the class is actually in.

Gradle error: Could not find method jar() for arguments

I'm writing a custom plugin which adds some data to manifest of Java project.
It looks something like this:
package com.example.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
public class ExamplePlugin implements Plugin<Project> {
def apply(Project project) {
project.jar() {
manifest {
attributes 'buildServer': checkIfIsBuildServer()
attributes 'personalBuild': checkIfIsPersonalBuild()
}
}
}
def checkIfIsBuildServer() {
return 'some result'
}
def checkIfIsPersonalBuild() {
return 'some result'
}
}
When I'm trying to apply it to some project, I get an error:
Could not find method jar() for arguments [com.example.gradle.ExamplePlugin$_apply_closure1#411e4f5e] on project ':SomeProject' of type org.gradle.api.Project.
I am reasonably sure this is some missing import. I don't have any idea how to determine what import it should be.
jar() isn’t a method on Project.
If I’m understanding your code correctly, what you are trying to do is configure the jar task that is created from the Java Plugin.
So you need to:
Get the task
Configure the task
Something like:
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Jar
public class ExamplePlugin implements Plugin<Project> {
def apply(Project project) {
project.afterEvaluate {
project.tasks.named(JavaPlugin.JAR_TASK_NAME, Jar) {
it.manifest {
attributes 'buildServer': checkIfIsBuildServer()
attributes 'personalBuild': checkIfIsPersonalBuild()
}
}
}
}
def checkIfIsBuildServer() {
'some result'
}
def checkIfIsPersonalBuild() {
'some result'
}
}
I highly recommend switch to Kotlin or Java for your plugin. It will make errors like this trivial to resolve and you will fully understand where things are coming from compared to Groovy’s dynamic nature.

Gradle plugin read configuration

I write a plugin for Gradle and I need to create dynamic tasks based on my extension configuration.
Example from build.gradle file:
exampleext {
t1 {
}
t2 {
}
}
So I want to create tasks like sometask#t1 and sometask#t2 and so on.
I could not find any info, how could I read this Closure configuration and use it for building these tasks? It's read in tasks only, but I want to use it before executing tasks.
Thanks in advance.
You could use Groovy's dynamic features:
class ExamplePlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("exampleext", ExampleExt, project)
}
}
class ExampleExt {
Project project
ExampleExt(Project project) {
this.project = project
}
def methodMissing(String name, Object args) {
def configClosure = args ? args[0] : {}
project.tasks.create(name: "sometask#$name", type: Copy, configClosure)
}
}
apply plugin: ExamplePlugin
exampleext {
t1 {
from "src/main/java"
into "$buildDir/tmp/main"
}
t2 {
from "src/test/java"
into "$buildDir/tmp/test"
}
}
You can have a look at https://github.com/tschulte/gradle-jnlp-plugin/blob/374360c118e2a7373ee2fa5be7d1b784240bb1aa/gradle-jnlp-plugin/src/main/groovy/de/gliderpilot/gradle/jnlp/war/GradleJnlpWarPluginExtension.groovy, where I allow dynamic task creation plus some more nesting. E.g.
jnlpWar {
versions {
"1.0"('org.example:application:1.0:webstart#zip')
}
}
is made possible by
void versions(Closure closure) {
closure.delegate = new Versions()
closure()
}
private class Versions {
#Override
Object invokeMethod(String name, Object args) {
project.configurations.maybeCreate(name)
return project.dependencies.invokeMethod(name, args)
}
}
However, maybe you should have a look at the incubating gradle model (https://docs.gradle.org/current/userguide/software_model.html).

How to implement this 'dynamic block' in Gradle?

I am new to gradle. So let straight to the point. I want to implement the block as below. Note that the libraries is dynamic, and available for other developers to add on for the needed libraries.
libraries {
slf4j 'org.slf4j:slf4j-api:1.7.21'
junit 'junit:junit:4.12'
}
So that I can call them out like this.
dependencies {
compile libraries.slf4j
testCompile libraries.junit
}
I am not sure how to make it. But I found some related solution from here. As shown below:
apply plugin: GreetingPlugin
greeting {
message 'Hi'
greeter 'Gradle'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("greeting", GreetingPluginExtension)
project.task('hello') {
doLast {
println "${project.greeting.message} from ${project.greeting.greeter}"
}
}
}
}
class GreetingPluginExtension {
String message
String greeter
}
The problem is as I add on to the greeting block, I need to declare them in GreetingPluginExtension as well. Any idea how to make it such that only update on greeting block?
What you need to do is to utilize groovy meta programming. Below you can find just a sample, however fully functional.
apply plugin: LibrariesPlugin
libraries {
slf4j 'org.slf4j:slf4j-api:1.7.21'
junit 'junit:junit:4.12'
}
class LibrariesPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("libraries", LibrariesPluginExtension)
project.task('printLib') {
doLast {
println "$project.libraries.slf4j"
println "$project.libraries.junit"
project.libraries.each {
println "$it.key -> $it.value"
}
}
}
}
}
class LibrariesPluginExtension {
Map libraries = [:]
def methodMissing(String name, args) {
// TODO you need to do some arg checking here
libraries[name] = args[0]
}
def propertyMissing(String name) {
// TODO same here
libraries[name]
}
Iterator iterator() {
libraries.iterator()
}
}

Is there a concept of test suites in Gradle/Spock land?

Groovy/Gradle project here that uses Spock for unit testing.
Does Spock and/or Gradle support test suites or named sets of tests? For reasons outside the scope of this question, there are certain Spock tests (Specifications) that the CI server just can't run.
So it would be great to divide all my app's Spock tests into two groups:
"ci-tests"; and
"local-only-tests"
And then perhaps we could invoke them via:
./gradlew test --suite ci-tests
etc. Is this possible? If so, what does the setup/config look like?
You can annotate the tests that should not run in your CI server with the Spock annotation #IgnoreIf( ).
See the documentation here: https://spockframework.github.io/spock/docs/1.0/extensions.html#_ignoreif
All you need to do is let the CI server set an environment variable, and exclude the test class if that variable is set.
Spock even have properties inside the closure to make it easy:
#IgnoreIf({ sys.isCiServer })
I would set up a submodule my-app-ci-test, with the following in build.gradle:
test {
enabled = false
}
task functionalTest(type: Test) {
}
Then you place your tests in src/test/groovy and run ./gradlew functionalTest.
Alternatively, you could include them in the same module and configure the test and functionalTest tasks with includes / excludes
test {
exclude '**/*FunctionalTest.groovy'
}
task functionalTest(type: Test) {
include '**/*FunctionalTest.groovy'
}
If you use Junit test-runner for Spock tests, you may use #Category annotation. Example by article and official documentation:
public interface FastTests {
}
public interface SlowTests {
}
public interface SmokeTests
}
public static class A {
#Test
public void a() {
fail();
}
#Category(SlowTests.class)
#Test
public void b() {
}
#Category({FastTests.class, SmokeTests.class})
#Test
public void c() {
}
}
#Category({SlowTests.class, FastTests.class})
public static class B {
#Test
public void d() {
}
}
test {
useJUnit {
includeCategories 'package.FastTests'
}
testLogging {
showStandardStreams = true
}
}
You can use the following SpockConfiguration.groovy to allow passing include/exclude via system properties
runner {
exclude {
System.properties['spock.exclude.annotations']
?.split(',')
*.trim()
?.each {
try {
annotation Class.forName(it)
println "Excluding ${it}"
} catch (ClassNotFoundException e) {
println "Can't load ${it}: ${e.message}"
}
}
}
include {
System.properties['spock.include.annotations']
?.split(',')
*.trim()
?.each {
try {
annotation Class.forName(it)
println "Including ${it}"
} catch (ClassNotFoundException e) {
println "Can't load ${it}: ${e.message}"
}
}
}
}

Resources