Gradle properties not visible inside extension container closure - gradle

I'm trying to write this custom plugin for Gradle but I'm stuck in properly passing parameters to the plugin.
inside the plugin I'm creating an extension like following:
#Override void apply(final Project p) {
p.extensions.create('myPlugin', MyPluginData.class)
then inside MyPluginData I'm handling def propertyMissing(String name, value) to receive the customer parameters I expect.
And finally inside the client application build.gradle I'm trying to configure the data:
println("From root value is " + SOME_VALUE)
myPlugin {
println("From plugin value is " + SOME_VALUE)
println("But from plugin 'findProperty' value is " + findProperty("SOME_VALUE"))
clientDataSet = {
data_1 = SOME_VALUE
data_2 = findProperty("SOME_VALUE")
data_3 = "this is a string"
SOME_VALUE is defined on my project gradle.properties, and I got the following log during build:
From root value is correct value from properties
From plugin value is null
But from plugin 'findProperty' value is correct value from properties
and then of course, while receiving data_1 SOME_VALUE is null, data_2 have the correct value and data 3 is the hard-coded string I passed.
My question:
What am I doing wrong or which configuration is missing on my plugin, so that the client application can directly reference properties from their gradle.properties files?
Edit: as requested on the comments
MyPluginData is simply extends HashMap<String, MyPluginDataSet> and MyPluginDataSet is just a few strings.
So inside propertyMissing I'm simply adding the property name to the map, and creating the MyPluginDataSet with the strings, (that later is used to generate custom tasks).
The missing property function:
def propertyMissing(String name, value) {
// Create the new data set and add to the map
def data = new MyPluginDataSet()
put(name, data)
// setup and execute the client closure to configure the data
def closure = value as Closure
closure.delegate = data
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure.run()
}

By making MyPluginData inherit from Map<>, I think you somehow "break" the property resolution process ( see ExtensionAware) and Gradle will not try to search for "SOME_VALUE" property in the different scopes (so it will not find this property from gradle properties extension)
Maybe you can try to simplify you MyPluginData class by storing an internal map instead of inheriting from Map ? something like that:
class MyPluginData {
Map<String, MyPluginDataSet> internalMap = new HashMap<>()
def propertyMissing(String name, value) {
println "Entering propertyMissing for name = $name"
// Create the new data set and add to the map
def data = new MyPluginDataSet()
internalMap.put(name, data)
// setup and execute the client closure to configure the data
def closure = value as Closure
closure.delegate = data
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure.run()
}
}

Related

MapStruct Spring Page to custom object conversion includes check

I am using MapStruct to convert a Page object to a custom object of my application. I am using this mapping in order to convert the content field of the Page object to a list of custom objects found in my data model:
#Mapping(target = "journeys", source = "content")
While this works OK and does convert the elements when content is present, this does not work correctly in case of no Page content. Taking a look at the code seems to show that the following check is added in the generated mapper class:
if ( page.hasContent() ) {
List<JourneyDateViewResponseDto> list = page.getContent();
journeyDateViewPageResponseDto.setJourneys( new ArrayList<JourneyDateViewResponseDto>( list ) );
}
When this is added the mapping action of the inner objects is omitted, meaning that I end up with a null list. I am not really sure as to why and how this check is added but I would like to find a way of disabling it and simply end up with an empty list of elements. Is there a way this can be done using MapStruct?
MapStruct has the concept of presence checkers (methods that have the pattern hasXXX). This is used to decide if a source property needs to be mapped.
In case you want to have a default value in your object I would suggest making sure that your object is instantiated with an empty collection or provide an #ObjectFactory for your object in which you are going to set the empty collection.
e.g.
Default value in class
public class JourneyDateViewPageResponseDto {
protected List<JourneyDateViewResponseDto> journeys = new ArrayList<>();
//...
}
Using #ObjectFactory
#Mapper
public interface MyMapper {
JourneyDateViewPageResponseDto map(Page< JourneyDateViewResponseDto> page);
#ObjectFactory
default JourneyDateViewPageResponseDto createDto() {
JourneyDateViewPageResponseDto dto = new JourneyDateViewPageResponseDto();
dto.setJourneys(new ArrayList<>());
return dto;
}
}
#Mapping(target = "journeys", source = "content", defaultExpression = "java(java.util.List.of())")

In Gradle, how do you perform validation of lazily evaluated properties (on extensions)?

Is there a way to validate a property value when the property is evaluated? I can't do it in the getter because that returns the Property object - I want the validation to run only when the actual value is calculated (i.e. I want to be lazy evaluation friendly).
They show extensions using the Property object here:
https://docs.gradle.org/current/userguide/lazy_configuration.html#connecting_properties_together
However, they don't explain how to do property validation when the value is calculated. Here is the snipet of code from the Gradle documentation provided example:
// A project extension
class MessageExtension {
// A configurable greeting
final Property<String> greeting
#javax.inject.Inject
MessageExtension(ObjectFactory objects) {
greeting = objects.property(String)
}
}
If I wanted to make sure the value of greeting was not equal to test, then how would I do that when it is evaluated?
For most use cases, it should be sufficient to just validate the property value once you resolve it in your task or in other internal parts of your plugin. Only a few extensions are actually designed to be consumed by other plugins or the build script.
Gradle does not provide some validation that can be attached to a property, however you can build this functionality on your own like in the example below:
class MessageExtension {
private final Property<String> _greeting
final Provider<String> greeting
#javax.inject.Inject
MessageExtension(ObjectFactory objects) {
_greeting = objects.property(String)
greeting = _greeting.map { value ->
if (value.equals('test'))
throw new RuntimeException('Invalid greeting')
return value
}
}
def setGreeting(String value) {
_greeting.set(value)
}
def setGreeting(Provider<String> value) {
_greeting.set(value)
}
}
project.extensions.create('message', MessageExtension)
message {
greeting = 'test'
}
println message.greeting.get()
I turned the Property into a backing field for a Provider that runs the validation when resolved. If you do not want to throw an exception, but just return an empty Provider, you may replace the map with a flatMap.

How do I access nested configuration values from my custom gradle extension?

I'm writing a custom gradle plugin that needs to accept an arbitrary number of nested parameters from the buildscript. Something like:
myPlugin{
configObjects = [
{
name="objectA",
value=5,
},
{
name="objectB",
value=9,
}
]
}
...where the number of items in configObjects, and the the values inside them is defined in whatever buildscript is importing the plugin.
So in my plugin code, I create an extension...
val config = extensions.create("myPlugin", myPluginTaskConfiguration::class.java, project)
tasks {
register<myPluginTask>("myPlugin") {
configObjects= config.configObjects
}
}
and a class defining the structure of the data received through the extension:
open class myPluginTaskConfiguration(project: Project) {
#Input
#Option(option="configObjects", description = "list of configObjects")
var configObjects:List<ConfigObject>?=null
}
Gradle allows me to specify the outer type, but apparently not the inner members. Running my plugin task I get the following error:
class build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 cannot be cast to class com.myplugin.ConfigObject (build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 is in unnamed module of loader org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler$ScriptClassLoader #224ed88; com.myplugin.ConfigObject is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader #72fe231e)
It's not clear to me what the type of the objects in the configObjects block is (well, apparently they're of type build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5, but I don't think that's something I can use at author-time)
How can I take the list of items from my groovy buildscript, and convert them into typed objects in my plugin (preferably in a way that allows the IDE to provide suggestions/hints to users editing the buildscript)?
#Input and #Option are for tasks. From the looks of it, you are using them for extensions.
There is no need to need to pass in a project instance in the constructor of a Task. All Tasks have a reference to the Project they belong to https://docs.gradle.org/current/javadoc/org/gradle/api/Task.html#getProject--
With that said, full working example in Kotlin would be:
open class MyPluginTaskConfiguration #Inject constructor(objects: ObjectFactory) {
val configObjects: ListProperty<Map<*, *>> = objects.listProperty()
}
open class MyPluginTask : DefaultTask() {
#Input
#Option(option="configObjects", description = "list of configObjects")
val configObjects: ListProperty<Map<*, *>> = project.objects.listProperty()
#TaskAction
fun printMessage() {
configObjects.get().forEach {
println("$it")
}
}
}
val config = extensions.create("myPlugin", MyPluginTaskConfiguration::class.java)
configure<MyPluginTaskConfiguration> {
configObjects.set(listOf(
mapOf<String, Any>(
"name" to "objectA",
"value" to 5
),
mapOf<String, Any>(
"name" to "objectB",
"value" to 9
)
))
}
tasks.register("myPlugin", MyPluginTask::class) {
configObjects.set(config.configObjects)
}
Executing the above produces:
./gradlew myPlugin
> Task :myPlugin
{name=objectA, value=5}
{name=objectB, value=9}
Refer to below doc for more details:
https://docs.gradle.org/current/userguide/lazy_configuration.html

Is there an equivalent in Dart of the instance_variable_set method in Ruby?

If not, is there anything like this on the horizon?
This is the one feature of JavaScript, Ruby, and Perl that I can't live without. I know you can fake it with a hash member, but I want to be able to create (arbitrary) "first class" members from a parser.
Currently there's nothing that can set a field that doesn't yet exist. The mirror API can be used to set fields that already exist, and may eventually be extended to support defining new fields dynamically.
You can also use the "noSuchMethod" method on a class to intercept setter / getter, and store the received value in a map.
For example (I can't remember the syntax exactly...):
class Foo {
var _dynamicProperties = new Map<String,Object>();
noSuchMethod(String function_name, List args) {
if (args.length == 0 && function_name.startsWith("get:")) {
// Synthetic getter
var property = function_name.replaceFirst("get:", "");
if (_dynamicProperties.containsKey(property)) {
return _dynamicProperties[property];
}
}
else if (args.length == 1 && function_name.startsWith("set:")) {
// Synthetic setter
var property = function_name.replaceFirst("set:", "");
// If the property doesn't exist, it will only be added
_dynamicProperties[property] = args[0];
return _dynamicProperties[property];
}
super.noSuchMethod(function_name, args)
}
}
And then you can use this in your code as follows:
var foo = new Foo();
foo.bar = "Hello, World!";
print(foo.bar);
Of course, this can lead to typos that will not be checked by the type checker, e.g.:
foo.bar = "Hello";
foo.baz = "Hello, World!"; // Typo, meant to update foo.bar.
There are ways you have type-checker validation by using redirecting factory constructors and an implied interface, but then it starts to get complicated.
Side note: This is what JsonObject uses to convert a JSON map to a class type syntax.

Set value in model when dynamically creating object of model

I have a bunch of models that may or may not have a property "CommonProperty". I want to set that property when I am creating a new object of a selected model. What I have so far, which works is:
ModuleItem model = db.ModuleItems.Find(ModuleItemID);
object o = GetModuleType(model.ControllerName);
private object GetModuleType(string ModelName)
{
string projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Type classtype = Type.GetType(string.Format("{0}.Models.{1}", projectName, ModelName));
PropertyInfo[] properties = classtype.GetProperties();
var classObject = classtype.GetConstructor(new Type[] { }).Invoke(null);
return classObject;
}
What I want to achieve is to set CommonProperty:
o.CommonProperty = DistinctValue;
I tried creating a class that all of my models inherit from with a virtual method and each model then has an override method. Because its not static I can't call it directly and if I create a new ModelBase then when calling the method it doesn't get overriden by the type that object "o" is. I looked at creating an interface but 1 I don't even know how these work, and 2 (probably because of 1) I am not even able to create a way of doing this without build errors.
When stepping through the code I can see all the properties of "o" using the quickwatch or intellisense or whatever it's called. Clearly it is being recognised as the correct type. I can't seem to be able to call the method though because it's not a recognised (or set) type during build only during runtime and therefore I can't build the solution.
What I would do is create a base model (eg: BaseModel) for viewmodels that have the property CommonProperty then check whether the model is of type BaseModel then set the property
if (obj is BaseModel)
{
obj.CommonProperty = DistinctValue;
}
As it turns out I was way overthinking this. Typically the answer is ridiculously simple. No base model necessary, no overrides, no virtuals, no instances, dump it in a try catch and just set the value of the context object.
object o = GetModuleType(model.ControllerName);
db.Entry(o).State = EntityState.Added;
try
{
db.Entry(o).CurrentValues["CommonProperty"] = DistinctValue;
}
catch (Exception err) { }
I did have to make sure I set the state BEFORE setting a current value otherwise it wasn't in the context (or something like that).

Resources