Why is PropertyState Necessary - gradle

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.

Related

MacOS specific code with Compose Multiplatform

I'm using Compose Multiplatform to write an app for MacOS. But I want to check if I have an internet connection. Preferably unmetered (so no hotspot). I have found some code for MacOS to check that. But when I create an expect class it complains about the actual class for jvmMain. But I want to put that part in macOSMain. If I try to add an empty class in jvmMain it takes that class instead of macOSMain. So I'm not sure how to implement this?
Here is a part of my build.gradle.kts:
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
macosArm64("macOS")
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
}
val jvmMain by getting {
dependsOn(commonMain)
dependencies {
implementation(compose.desktop.currentOs)
}
}
val macOSMain by getting {
dependsOn(commonMain)
}
}
}
In commonMain I put this for testing:
expect class Test {
fun test(): String
}
In macOSMain I put this:
actual class Test {
actual fun test(): String {
return "MacOS"
}
}
And in jvmMain I put this:
actual class Test {
actual fun test(): String {
return "JVM"
}
}
And to call the function in jvmMain:
fun main() = application {
println("test: ${Test().test()}")
Window(onCloseRequest = ::exitApplication) {
App()
}
}
Which unfortunately prints "JVM". What do I need to do to make it print "MacOS"? Preferably without implementing the class in jvmMain. I also tried expect class in jvmMain, but it then complained about the function not being there in jvmMain.

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 only register Transform in debug buildtype

I wrote a custom gradle plugin for android app, and I register a transform, but i just want to register it when current buildtype is debug. How can i achieve it.
public class CustomPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.registerTransform(new CustomTranform());
}
}
You can get variant name from the context in which the transform is run: javadoc
Which means you can write your transform this way:
class CustomTranform extends Transform {
#Override
public void transform(TransformInvocation invocation) {
String variantName = invocation.getContext().getVariantName();
if (variantName.toLowerCase().endsWith("debug")) {
// actual transform
} else {
// no-op transform
}
}
...
}

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()
}
}

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

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()
}
}

Resources