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).
Related
Suppose I'm developing a Gradle plugin and the inputs that some of the tasks the plugin configures depend on how it is configured via an extension. For example:
class MyTask extends DefaultTask {
#InputFile
File toTrack
#TaskAction
def run() {
println("The file now contains ${toTrack.text}")
}
}
class MyConfig {
File toTrack = new File('bad-default.txt')
}
class MyPlugin implements Plugin<Project> {
#Override
def apply(Project project) {
project.with {
extensions.create('config', MyConfig)
task('printChanges', type: MyTask) {
toTrack = config.toTrack
}
}
}
}
Unfortunately, this doesn't work correctly. The problem is that if I have a build.gradle like:
apply plugin: my-plugin
config {
toTrack = file('file-to-track.txt')
}
where I've specified a file to track, Gradle will evaluate the #InputFile on my task before the config block is run so it'll decide if the task is up to date or not by looking at bad-default.txt rather than file-to-track.txt.
The recommended fix to this seems to be to use PropertyState like so:
class MyTask extends DefaultTask {
PropertyState<File> toTrack = project.property(File)
#InputFile
File getToTrack { return toTrack.get() }
#TaskAction
def run() {
println("The file now contains ${toTrack.get().text}")
}
}
class MyConfig {
private PropertyState<File> toTrack
MyConfig(Project project) {
toTrack = = project.property(File)
toTrack.set('bad-default.txt')
}
void setToTrack(File fileToTrack) { toTrack.set(fileToTrack) }
}
class MyPlugin implements Plugin<Project> {
#Override
def apply(Project project) {
project.with {
extensions.create('config', MyConfig)
task('printChanges', type: MyTask) {
toTrack = config.toTrack
}
}
}
}
That works but it seems very verbose and the PropertyState stuff seems entirely unnecessary. It seems like the real fix was simply to change the #InputFile annotation to be on a getter instead of having it be on the property. In other words, I believe the following has the same effect and is less code and easier to understand:
class MyTask extends DefaultTask {
File toTrack
// This is the only change: put the annotation on a getter
#InputFile
File getToTrack() { return toTrack }
#TaskAction
def run() {
println("The file now contains ${toTrack.text}")
}
}
class MyConfig {
File toTrack = new File('bad-default.txt')
}
class MyPlugin implements Plugin<Project> {
#Override
def apply(Project project) {
project.with {
extensions.create('config', MyConfig)
task('printChanges', type: MyTask) {
toTrack = config.toTrack
}
}
}
}
With some experiments this does seem to have the desired effect. What am I missing? Is there ever a time when PropertyState is necessary?
The problem is not the time #InputFile is evaluated. #InputFile is evaluated just before the task is executed, so after the configuration phase has finished and during gradles execution phase. The problem PropertyState is solving is the wiring between an extension and a task.
let's look again on your apply method:
def apply(Project project) {
project.with {
extensions.create('config', MyConfig)
task('printChanges', type: MyTask) {
toTrack = config.toTrack
}
}
}
here you:
1) Create your custom extension with the default value provided in the MyConfig class.
2) Link the value currently set in MyConfig to the MyTask toTrack property.
Now looking at the plugin usage:
apply plugin: my-plugin
config {
toTrack = file('file-to-track.txt')
}
here you:
1) Apply the plugin (and basically executing the apply method of your plugin).
2) Reconfigure the MyConfig#toTrack extension property.
But what isn't happening here, is updating the value in the printChanges task. That's what PropertyState is solving. It has nothing todo with Task inputs and outputs evaluation.
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()
}
}
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()
}
}
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}"
}
}
}
}
I have implemented a Gradle plugin using the Gradle DSL style. The plugin is adding multiple aspects such as a adding a custom task, and configuring more other tasks. Overall, the plugin is generating some metadata property file in a source folder that must be configured by a plugin extension.
apply plugin: 'artifactMetadata'
// configure the path for the metadata
artifactMetadata {
destinationDirectory = "src/main/generated/otherlocation/resources"
}
I have been able to figure out how to configure the task using the extension properties, however it's tricking me with the remaining stuff. What is a good approach to configure the source set, the clean task and the idea plugin (see the #n: TODO comments in the plugin code below)? The implementation below will always use the default value, not the one injected through the plugin extension.
class ArtifactMetadataPlugin implements Plugin<Project> {
public static final String EXTENSION_NAME = 'artifactMetadata'
public static final String TASK_NAME = 'generateArtifactMetadata'
void apply(Project project) {
createExtension(project)
project.configure (project) {
task (TASK_NAME, type: GenerateArtifactMetadata) {
group = project.group
artifact = project.name
version = project.version.toString()
}
sourceSets {
main {
// #1:TODO to get the plugin extension property current value here output.dir(project.artifactMetadata.destinationDirectory, builtBy: TASK_NAME)
resources.srcDirs += file(project.artifactMetadata.destinationDirectory)
}
}
clean {
// #2:TODO get the plugin extension property here
delete file(project.artifactMetadata.destinationDirectory)
}
if (project.plugins.hasPlugin(IdeaPlugin)) {
idea {
module {
// #3:TODO get the plugin extension property here
sourceDirs += file(project.artifactMetadata.destinationDirectory)
}
}
}
}
project.afterEvaluate {
def extension = project.extensions.findByName(EXTENSION_NAME)
project.tasks.withType(GenerateArtifactMetadata).all { task ->
task.destinationDirectory = project.file(extension.destinationDirectory)
}
}
}
private static void createExtension(Project project) {
def extension = project.extensions.create(EXTENSION_NAME, ArtifactMetadataPluginExtension)
extension.with {
destinationDirectory = "src/main/generated/artifactinfo/resources"
}
}
}