:wave: hi mates!, I developed a Gradle plugin some...
# community-support
d
šŸ‘‹ hi mates!, I developed a Gradle plugin some months ago to manage repositories configuration dinamically, it configures AWS CA repositories, managing dynamic credentials resolution and so on.... Those repositories are configured globally. The plugin works pretty well on multiple projects, but it failed recently with a new one that has a custom configuration directly specified in the
build.gradle
file, the custom configuration works to resolve a single dependency, that is used by a custom task to unzip its content and move some webapp static files to a folder before compiling the final application. After spending hours and hours doing multiple tries, I found the root issue, the dependency of the custom configuration is resolved if I configure the repository statically in project.repositories{} (from the plugin nor the
build.gradle
's project file), but it fails (401 Auth error) if the repository is configured from
project.afterEvaluate{}
, and I need to configure those repositories from inside
afterEvaluate
cause there is a Gradle extension with dynamic data of those repositories configured at the `build.gradle`'s project level. Any clue, help, or something that I could do? thank you
v
I need to configure those repositories from inside
afterEvaluate
This part is most probably wrong, the "need to".
afterEvaluate
is almost always a bad idea. The main earnings from using it are ordering problems, timing problems, and race conditions. Using
afterEvaluate
instead of finding a proper alternative way to do something is like calling
Platform.runLater
or
SwingUtilities.invokeLater
to "fix" a GUI problem. It usually just shifts the problem to a later more hard to find, more hard to debug, more hard to reproduce, and more hard to fix point in time. There are rare cases where
afterEvaluate
is necessary nowadays, for example if you need to interact with some plugin that itself badly uses
afterEvaluate
with which you have to cooperate, or if you need to bridge
Property
-based things with legacy primitive-based things. So the best fix indeed would be to change your plugin to remove the need to use
afterEvaluate
. You could for example in your extension instead of having some properties, have a function that get those values as arguments and then do the configuration based on those values from within the function in the extension. Or if you prefer the DSL-like configuration you could do it in a way that looks
myExtension { whatever { propertyA = "a" } }
where
whatever
is a function that takes an
Action<MyPropertyHolder>
and in the function you create an instance of
MyPropertyHolder
, give that to the supplied action so that the properties get set, and after that again right away do the configuration. That you get this 401 error probably means, that your plugins configuration is not done yet and thus the repository answers with 401 which makes resolution fail. So this probably means, that this build we are talking about is resolving this custom configuration at configuration time, which in itself again is a bad idea and discouraged. There are even voices to forbid such a premature resolution: https://github.com/gradle/gradle/issues/2298 It is very unlikely that the build in question really needs to do the resolution at configuration time but just does it lightly, so this should most probably also be improved to only do the resolution at execution time. How to properly do that I cannot answer right away without seeing what that build actually does exactly there.
d
tyvm for answering @Vampire, I agree that if there is any approach to avoid the values resolution of the extension that would be a great idea, but I didn't find a proper doc to do it, is out there any?, could you elaborate a little bit more about how to do it? Indeed, the
401
is due to Gradle trying to resolve the dependency from the custom configuration before/parallel the repository is being configured from inside
afterEvaluate
.
"It is very unlikely that the build in question really needs to do the resolution at configuration time but just does it lightly, so this should most probably also be improved to only do the resolution at execution time."
Regarding this, before coming here, my research pointed to do something like this, nor force the custom config to re-evaluate the repositories later on... but I didn't find doc about it either. thank you
v
What elaboration do you need? I can hardly say more than I said without seeing some actual code. But the most typical situation I told you two ways to do it. But as I said, for a really concrete advice I'd need to see some code. Optimally some MCVE that demonstrates why you think
afterEvaluate
is necessary in that situation.
d
tyvm @Vampire for the guidance, it put me on the right direction, rightly all your points are valid (now I understand why, despite there are other points I don't understand), those tasks using the dependency from the custom configuration were wrongly configured, so they were running tasks at configuration time incurring on race conditions with the plugin. I fixed them by changing
from.zipTree(configurations.customConfig.singleFile).matching ...
by
from {
zipTree(configurations.customConfig.singleFile).matching ...
}
ChatGPT guided me on understanding that apparently by calling directly the from object methods, the action performs at the configuration time, but by doing it through the closure it does it at the execution time. (It works and if it is right I would like to argue that it is so confusing) thank you again @Vampire
v
despite there are other points I don't understand
Feel free to ask about them, here if relevant to the thread or maybe better in new threads if unrelated or only loosely related
I fixed them by changing
Yeah, that was the one part I mentioned. Still it would be better to also fix the other part, that
afterEvaluate
is actually used. šŸ™‚
> from.zipTree(configurations.customConfig.singleFile).matching ...
by
> from {
> zipTree(configurations.customConfig.singleFile).matching ...
> }
Cannot say much to that, as I have no idea what
from
is or which
zipTree
function. Because in Gradle built-in is
zipTree
only on
ArchiveOperations
,
Project
, and
Script
and to me it does not look like
from
should be any of those. So the difference between
from.zipTree
and
from { zipTree }
seems to not come from Gradle itself. Though it could well be (but does not always have to) that something you have in a lambda is evaluated at least deferred while without is executed directly. There are other cases in the Gradle API where it indeed is like that.
ChatGPT guided me on understanding
I'm sure you know it already, but never trust anything ChatGPT says. ChatGPT is utterly bad in giving correct answers. It is pretty good though in giving answers that look correct. It is nice to do work for you, you are too lazy to do yourself but could have done yourself (the good kind of lazy, not the bad one). But you always have to understand what it told you, and correct the non-sense it did. Asking about things you do not know yourself is pretty problematic, as it always gives an answer that looks very reasonable but often is wrong or at least incomplete.
d
for giving you more context about those changes. • That
from
comes from
org.gradle.api.tasks.AbstractCopyTask#from(java.lang.Object...)
(version
8.2
) •
zipTree
comes from
org.gradle.api.Project#zipTree
About ChatGPT I agree, and indeed using it was a consequence..., I use it a lot with personal projects working with common and well-known tech stacks which I'm not very used to, simple things like "code a PieChart with MUI using the series from the entries x, y, z in the map", and it is really nice there avoiding hours of reading (at the cost of learning but you now, individual performance matters...). With challenges like this one, where I don't know the context, I always try to go to the official documentation, as I stated in the first message, but not being able to find what I was looking for, partially because I didn't know about what I was looking for at the 100%, I found on it a suitable way to find something. It was useful sometimes, but indeed it was the "best" liar the most of the times and for this particular investigation I ended up here with much better result. I think develop Gradle Plugins is not an easy thing because there are many understanding points hided which make it a real challenge, you realize it when things start to breaking apart, I feel that there are many documentation in internet, like SO threads, blogs and so on which promote bad practices (I learnt
afterEvaluate
in one of those), which is very scaring.... So I think the best bet to avoid most of the community following bad practices would be by having the best documentation as possible.
v
for giving you more context about those changes.
• That
from
comes from
org.gradle.api.tasks.AbstractCopyTask#from(java.lang.Object...)
(version
8.2
)
•
zipTree
comes from
org.gradle.api.Project#zipTree
Then make sure in the future to copy code, not retype it. Because with those,
from.zipTree(...)
would not compile, but probably
from(zipTree(...))
or
from zipTree(...)
as you are still on Groovy DSL šŸ˜‰ A better lazy evaluation would be
Copy code
from(configurations.customConfig.elements.map {
    assert it.size() == 1
    zipTree(it.first()).matching ...
})
there are many documentation in internet, like SO threads, blogs and so on which promote bad practices
Yes, unfortunately there is many bad advice out there, or also things that might have been best practice or sole option some years ago but are highly discouraged now.