Yeah, sorry. That version was really rough. You can’t do exactly that because there’s a limitation that whatever you apply the fragment to:
needs to have something that can receive the top level item in the fragment. In this case, the NamedDomainObjectContainer
doesn’t have a method/property called h2
, so it fails.
We can work around this in several ways, but the question is going to be which one makes the most sense for your requirements. While you can certainly have each fragment contain the entire databases {}
block, you can do at least one better. Here’s some options. Hopefully, it’s clear what’s what as I modify some working code to align with code and naming from your examples.
#1. Remove one level from the fragment and apply to databases
.
// plugin
project.fileTree(profileDirectory).matching(files -> files.include("**/*.gradle")).forEach(file -> {
project.apply(Helper.asMap("from", file, "to", dslExtension));
});
You include profiles {}
, but not databases {}
in the fragment:
// h2.gradle
profiles {
h2 {
...
}
}
#2. Add a helper method in the extension for adding from the fragment.
The plugin code stays the same, and you apply to:
the dslExtension
, but you add a method to the extension that will end up executing the fragment code against a newly created :
void profile(String name, Action<DatabaseProfile> profileAction) {
DatabaseProfile profile = new DatabaseProfile(name);
profileAction.execute(profile);
profiles.add(profile);
}
Your syntax doesn’t need to change if you inline the profiles in the main build.gradle
file, but if you use a fragment, you need to call the profile
method with the name of the profile and the configuration closure:
// h2.gradle
profile('h2') {
...
}
#3. Remove the wrapper entirely and only include the properties directly on the profile in the fragment.
You end up using the file name as the name for the NamedDomainObjectContainer
because it’s all you have at the time you need to create the object:
// plugin
project.fileTree(profileDirectory).matching(files -> files.include("**/*.gradle")).forEach(file -> {
DatabaseProfile profile = new DatabaseProfile(file.getName().replace(".gradle", ""));
project.apply(Helper.asMap("from", file, "to", profile));
dslExtension.getProfiles().add(profile);
});
Not very interesting, but the fragment is literally the ...
from the earlier examples. This might make your Gradle fragment look a lot closer to the property file version.
// h2.gradle
...
#4. Use a Groovy class that can intercept the missing method call and properly add it to the NamedDomainObjectContainer
,
This gets you closest to the original version, but requires the most amount of code. First create the wrapper that will handle the calls:
class DatabaseProfilesDelegate {
private final NamedDomainObjectCollection<DatabaseProfile> profiles;
DatabaseProfilesDelegate(NamedDomainObjectCollection<DatabaseProfile> profiles) {
this.profiles = profiles;
}
void methodMissing(String name, args) {
DatabaseProfile profile = new DatabaseProfile(name);
Closure closure = args[0];
closure.delegate = profile
closure.call()
profiles.add(profile);
}
}
Then you’ll use that class as the target of the to:
// plugin
project.fileTree(profileDirectory).matching(files -> files.include("**/*.gradle")).forEach(file -> {
project.apply(Helper.asMap("from", file, "to", new DatabaseProfilesDelegate(dslExtension.getProfiles())));
});
With this, the fragment can look exactly the same as if it were in the profiles {}
block of the extension:
// h2.gradle
h2 {
...
}