This message was deleted.
# community-support
s
This message was deleted.
e
This may be useful: Project#property(String) What happens if in your task's
doLast
you instead use
project.property("greeting.message")
to retrieve your property?
a
this works in the case where I use the command line but if I only set
greeting.message
in the extension block:
Copy code
greeting {
   message="greeting1"
}
then it results in an error:
Copy code
* What went wrong:
Execution failed for task ':greetingTask'.
> Could not get unknown property 'greeting.message' for root project 'proj1' of type org.gradle.api.Project.
j
The most ‘modern’ way to access such a Gradle property would be through
project.getProviders().gradleProperty("greetings.message")
You then have to pass that to your extension. For example instead of
getMessage().convention("greeting0")
above, you could do
getMessage().convention(project.getProviders().gradleProperty("greetings.message"))
This is a common problem/pattern. That’s why @benedikt and I have created a plugin that basically generates this setup for you. Maybe it fits your use case: https://github.com/gradlex-org/build-parameters
👀 1
👍 1
a
This requires the
GreetingPluginExtension
class to have access to the
project
instance
j
Yes… right. Sorry. I typically set the convention in the plugin and not in the constructor. So do
greeting.getMessage().convention(project.getProviders().gradleProperty("greetings.message"))
right after you created the extension.
Or you can add
Project project
as parameter to the constructor and add
@Inject
to the constructor.
With the first solution, the extension can also be an
interface
without a constructor.
a
Do you have a complete code example that uses this pattern?
Also, how do I set a default value for the property when I use this pattern?
I'm getting this error:
Copy code
* What went wrong:
A problem was found with the configuration of task ':greetingTask' (type 'GreetingTask').
  - In plugin 'extension_example.greeting' type 'extension_example.GreetingTask' property 'message' doesn't have a configured value.
    
    Reason: This property isn't marked as optional and no value has been configured.
    
    Possible solutions:
      1. Assign a value to 'message'.
      2. Mark property 'message' as optional.
    
    Please refer to <https://docs.gradle.org/7.5.1/userguide/validation_problems.html#value_not_set> for more details about this problem.
j
Yes, if you run without
-P
, right? You should be able to chain another convention:
greeting.getMessage().convention(project.getProviders().gradleProperty("greetings.message").convention("defaultMessage"))
If you clone this example: https://github.com/gradlex-org/build-parameters/tree/main/samples/basics and
build
it, the code generated by the “build-parameters” plugin should follow this pattern. You can find that in
build/generated-src
👀 1
a
Thank you so much! Taking a look
OK the
convention
chaining works in providing a default value, but it doesn't identify it when I add
-Pgreeting.message="greeting2"
to the command line
oh silly me I was mixing
greeting
and
greetings
with the extra s
j
Np. I think I introduced that extra
s
when I gave the samples. 😄
Glad that it works now
a
I corrected the mixup and it still doesn't work 😕
I'll try using your plugin on my greeting example
j
How do you connect the Extension to the Task Input? That bit is missing as far as I can tell in your original post.
e
greeting.getMessage().convention(project.getProviders().gradleProperty("greetings.message")
means the value of the extension property will take priority over the command line parameter, correct? Generally you want it to be the other way around where the command line parameter takes priority, right?
a
I would like the command line param to take priority, yes
@Jendrik Johannes here's my current setup:
Copy code
// GreetingsPlugin.java

package extension_example;

import org.gradle.api.Project;
import org.gradle.api.Plugin;
import org.gradle.api.provider.Property;

import javax.inject.Inject;

abstract class GreetingPluginExtension {
    abstract Property<String> getMessage();

    @Inject
    public GreetingPluginExtension(Project project) {
        getMessage().convention(project.getProviders().gradleProperty("greeting.message")).convention("greeting0");
    }
}

public class GreetingsPlugin implements Plugin<Project> {
    public void apply(Project project) {
        GreetingPluginExtension extension = project.getExtensions().create("greeting", GreetingPluginExtension.class);
        project.getTasks().register("greetingTask", task -> {
            System.out.println(extension.getMessage().get());
        });
    }
}
Copy code
// build.gradle
plugins {
    id 'extension_example.greeting' version 'unspecified'
}

repositories {
    mavenLocal()
}

greeting {
    message = 'greeting1'
}
when I comment out the
greeing{}
block and run
gradle greetingTask -Pgreeting.message="greeting2"
I get
Copy code
> Task :greetingTask
greeting0
j
The second
convention
is in the wrong place so that it overrides the first one: This
...gradleProperty("greeting.message")).convention("greeting0")
should be
...gradleProperty("greeting.message").convention("greeting0"))
a
so like this?:
getMessage().convention(project.getProviders().gradleProperty("greeting.message").convention("greeting0"));
👍 1
Copy code
error: cannot find symbol
        getMessage().convention(project.getProviders().gradleProperty("greeting.message").convention("greeting0"));
                                                                                         ^
  symbol:   method convention(java.lang.String)
  location: interface org.gradle.api.provider.Provider<java.lang.String>
j
Ah right. So I was wrong. Shouldn’t try this from memory.
a
Would you like me to upload the complete example to a github repo?
e
When you access the property from within your task, try something like:
Copy code
String message = project.getProviders().gradleProperty("greeting.message").getOrElse(extension.getMessage().get())
a
Jendrik wrote something and then deleted it?
I tried doing this in the constructor:
getMessage().convention(project.getProviders().gradleProperty("greeting.message").orElse("message0"));
this works, however it treats the value in the
greeting {}
block with MORE precedence over the command line
e
That's what I was afraid of.
j
Yes. Sorry for deleting that again. That’s what I meant.
But yes, the order is wrong for your case.
a
Eric your example works perfectly
🙌 1
😮 1
j
Yes, in that case, what Eric proposes is better.
a
however it is quite cumbersome to add that line to each and every property in the plugin i'm migrating.. hmmm
I still havent given Jendrik's build parameter plugin a try
j
But, if you want to treat
message
as a task input later, you have to check. Because accessing properties directly from within the task is bypassing declaring them as inputs correctly.
a
I'll try using
@Input
now and see if that works
e
Jendrik that's a great point.
j
The plugin, basically neglects the use case you have though. It just gives you an extension to “build parameters” (Gradle properties) for which you can define a default value. You can’t change these values anymore in the build.
This is on purpose, because we think it is just overly complicated if you set/change the value in different places in the build. Maybe just setting a default is enough.
a
I'm not sure I understand your point. The build parameters plugin does not allow setting a value from several places with a defined precedence? (such as command line > extension block > default value)
j
In you case, you can also set the value where you now have
.convention("greeting0")
and then not change it in another place in the build again. Then the
-P
value wins over the value you set there. And that’s more or less what the plugin is also doing. Only that the code the plugin generates actually prevents you from changing the value (it’s using Providers instead of Properties).
It allows you to use
-P
and you can define a default value. You can not change things in the extension block (the extension is read-only you could say)
a
Oof this is getting somewhat complicated 😵‍💫
I think I need to read up on Gradle providers to get a better grip of this
j
Sorry. Some topics are mixed up here now.
a
Regardless, I'm grateful for both of you assisting me 🙏
🙌 1
Gonna try to dive deeper into this tomorrow.
j
The build parameters plugin is probably not a fit for you. It’s for developing products/applications mostly. If you develop a plugin that stands for it own, it’s probably not the right fit
👍 1
Sorry if I confused you. 🙂
a
You did the opposite of confuse me!
You provided enough clarity for me to understand what I dont understand
😅 1
namely: providers, chaining conventions, and how they interact with task inputs
j
But it’s an interesting point. I think what you want to have sounds like something that _should be easy to do_™️
a
Hahaha
j
But apparently it isn’t…. now I have to think about this as well
a
there's another thing we haven't even dove into that is also relevant in this matter
the numerous ways to define properties
1. Command line arguments 2. System properties 3. Environment variables 4. Gradle properties defined in user's home
gradle.properties
file 5. Gradle properties defined in project's
gradle.properties
file 6. Gradle properties defined in gradle installation directory
gradle.properties
file 7. Project properties defined in
ext {}
block 8. Project properties defined in extension blocks
I probably missed something
j
I think you can solve the priority like this: • For the extension, you just set the default:
getMessage().convention("greeting0")
• But for the task you then involve the
greeting.message
property
getMessageTaskInput().set(project.getProviders().gradleProperty("greeting.message")).convention(extension.getMessage())
It’s similar to what Eric proposed, only that you do it on the task inputs and not inside the task action.
the numerous ways to define properties
Yes. That’s why I would avoid using them if you can. 😄 In the context of the build parameters plugin, we basically said that we want to support
-P
. The other things automatically work as well, but we do not encourage using them.
So for everything that’s only inside the build (can be defined in build scripts) you do not use “gradle properties” at all, but rely on extensions.
And only if you absolutely want configurability from the outside you add support through
-P
as we have discussed here.
a
I do want configurability from outside because my plugin's users already use this in numerous places and I do not want to take this ability away and break their build and CI jobs etc
j
👍 I understand. If you use
project.getProviders().gradleProperty("greeting.message")
all the ways to define properties from the outside still work (-P, gradle.properties, …)
a
I will do that then
Thank you
j
The things from inside the build do not work through that I think (7. ext {}, 8. project properties). But my understanding is that you want to replace that with the extension.
a
Yes
👍 1
v
Just as a general additional thought. If it is only about configurability through command line (i. e. not through
gradle.properties
), you could also consider using
@Option
properties on your tasks.
💯 2
a
Coming back to this now, I see that the
gradleProperty
method returns a
Provider<String>
, so the code we wrote above only works for
String
properties
How do I do this with properties of other types?
v
.map
it to the type you want to have
👍 2
j
E.g.
Copy code
gradleProperty("abc").map { it.toInt() }
👍 2
m
Great discussion, learned so much!
👌 1
🙌 3