-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Maven plugin improvements #20695
base: main
Are you sure you want to change the base?
Conversation
Make everything accessible/overrideable downstram
Yes not everyone is using Eclipse or want's to have Eclipse specific dependencies...
|
Awesome @AB-xdev 👏 Does this also fix performance regressions in the development mode 🤔 |
@mstahv We only changed the Maven Plugin and nothing noteworthy of the |
@AB-xdev thank you so much for the contribution. The PR contains many changes, so it will take some time to review it. A couple of questions and notes:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a first review round and added some comments.
I still need to test the changes locally because I currently see several test failures.
I'll continue the review as soon as I get the validation pass locally.
public static Method findMethod(Class<?> cls, String methodName, | ||
Class<?>... parameterTypes) throws ExceptionInInitializerError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this public method removed? If it is not used anywhere, we can consider deprecating it, but it cannot be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this public method removed?
It was only used in the plugin and is also marked as internal
(in package)
If it is not used anywhere, we can consider deprecating it, but it cannot be removed.
We can change it back if you like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it is internal
, usually we try not to remove public API in minor or releases.
So, yes, please bring the method back and mark it as @Deprecated(forRemoval)
. Also, the Javadoc @deprecation
should state that there will be no replacement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restored and marked as deprecated
if (missingDependencyMessage == null) { | ||
missingDependencyMessage = text -> { | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would instantiating an Optional be more performant than having a no-op consumer?
Otherwise, I would leave the code as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used an Optional
further down in the if
s as I found it more readable and it makes more sense there (the previously created consumer may have never been used in the if
s below).
As an alternative one can also do a null check, like:
if(...) {
if (missingDependencyMessage != null) { // Potential replacement for Optional
missingDependencyMessage.accept(...);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I would prefer the null check in this case, since there is no pipeline built on top of the Optional
.
import java.util.Set; | ||
|
||
|
||
public class DefaultReflector implements Reflector { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need Javadoc, both on type definition and on public and protected methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the class final?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the class final?
What's the advantage of this?
Currently one can override the class if required. If it's final
that's no longer possible.
this.isolatedClassLoader = Objects.requireNonNull(isolatedClassLoader); | ||
} | ||
|
||
public DefaultReflector(final Object copyFromOtherClassLoader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make this constructor package-protected, as it looks like to me, it should be used only internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make this constructor package-protected, as it looks like to me, it should be used only internally.
Would also keep it public so one can override/instantiate it if required.
final Class<?> classFinderImplClass = getIsolatedClassLoader().loadClass( | ||
ReflectionsClassFinder.class.getName()); | ||
classFinder = classFinderImplClass | ||
.getConstructor(ClassLoader.class, URL[].class) | ||
.newInstance( | ||
isolatedClassLoader, | ||
isolatedClassLoader.urlsToScan()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reasons to get the class loader instance both by field access and getIsolatedClassLoader()
method?
If not, I would use the field, so we are sure to use the exactly same instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
Overlooked that, it should probably use the method too like on all other occurrence.
I would prefer the method since it can be easier overridden and is part of the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
||
private Consumer<File> buildContextRefresher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I would prefer to preserve this consumer here at the moment. I don't see any performance impact on having it. Or do you have any evidence that keeping this field negatively impacts on performance?
import java.util.List; | ||
|
||
|
||
public class FastReflectorIsolationConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I wonder if we can find a better name. However, I don't have a suggestion right now: I'll think about ir.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Javadocs are missing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed it to FastReflectorConfig
and created Isolation
subclass
} | ||
|
||
public static class ArtifactSelectors { | ||
private boolean defaults = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented the field correctly.
This defines if the defaults should be used, like
https://github.com/AB-xdev/flow/blob/d316e63d594e8ab979b9011019be0075ed434741/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/DefaultReflectorProvider.java#L41-L48
* Please note that this only works for inclusions, not exclusions. | ||
* </p> | ||
*/ | ||
private boolean scan = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find where this flag is used. Can you please explain it to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved the JavaDoc.
Generally speaking if this is false
than it will only be used for class loading, otherwise if it's true
it will be used for scanning + class loading
protected boolean checkIfArtifactSelectorMatches( | ||
final FastReflectorIsolationConfig.ArtifactSelector selector, | ||
final Artifact artifact) { | ||
if (selector.getGroupId() != null && !compareSelector(selector.getGroupId(), artifact.getGroupId())) { | ||
return false; | ||
} | ||
return selector.getArtifactId() == null | ||
|| compareSelector(selector.getArtifactId(), artifact.getArtifactId()); | ||
} | ||
|
||
protected boolean compareSelector(final String selector, final String target) { | ||
if (selector == null || target == null) { | ||
return false; | ||
} | ||
|
||
if (selector.endsWith("*") && selector.startsWith("*")) { | ||
return target.contains(selector.substring(1, selector.length() - 1)); | ||
} else if (selector.endsWith("*")) { | ||
return target.startsWith(selector.substring(0, selector.length() - 1)); | ||
} else if (selector.startsWith("*")) { | ||
return target.endsWith(selector.substring(1)); | ||
} else { | ||
return selector.equals(target); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these methods can be moved to ArtifactSelector
. Something like ArtifactSelector.matches(Artifact)
.
It would also simplify adding tests for the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep this here since the Config class is just a data holder which contains no logic.
I created #20776, rebased on main, to test the changes.
I'll continue the investigation, pushing changes on the linked PR. |
|
||
// If a jar is inside a target folder, it's likely a sibling project | ||
if (fastReflectorIsolationConfig.isIncludeFromTargetDirectory() | ||
&& "target".equals(artifact.getFile().getParentFile().getName())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Output directory can be configured in maven build, so it would be better to avoid hard-coding "target" and get the value from the Maven project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Currently, the changes in the PR will break projects using The PR is quite complex and introduces multiple performance improvements, so I would suggest splitting it into several smaller PR (the best option would be one per each added configuration) so that we can measure the effective impact of every single change. |
Also note that Hilla plugin recently removed the need for executing |
Wow, that's a lot of comments. I will try to answer them all.
So it's possible that one can override them without getting having to use reflection or copying code ^^
Correct
Yes, the original only contained
I didn't know that.
I already considered breaking it down into smaller bits, however since the reflector is like 90% of the change and the other changes sometimes depend on one another I discarded this option.
That's nice. WIP |
An additional note about the changes in visibility: these changes are significantly increasing the API surface that we would be promising to keep stable between versions. Before committing to something like that, we would want to better understand what potential benefits you expect to get from that. About the provided scoped dependencies:
My concern is that this modification might add more dependencies to the class loader, potentially leading to behavior changes compared to the original code and introducing a breaking change. However, it could make sense to consider also provided dependency. Needs to be investigated. |
This check is only needed once during migration and never again.
* Reflector now uses a Builder-like pattern and can be replaced with a custom implementation if required * Default implementation only includes/scans only actually used dependencies and not everything else. Special customization may still be required for certain projects * Replaced``getOrCreateReflector`` with ``getClassFinder`` * Removed some unused methods * Simplified and extended logging
d316e63
to
1fc551a
Compare
Thank you for the additional feedback.
Generally speaking I want to be able to overwrite/customize your code without constantly having to copy it when an update happens because some method is private or something like that. Since I had this problem now for the like 50th time, I decided to write a short blog post about it.
I was not aware that you have to keep the plugin "stable" (you know since the average Vaadin user will likely never extend it).
It should make sense to include provided dependencies as the class-loading will otherwise very likely fail (e.g. if a class extends from another one that is inside a provided dependency).
My opinion here is that the hilla Plugin can implement these itself since only hilla needs them.
And now my extensibility effort from above pays off: Hilla can simply use a derivative of
The new system is currently designed in a way that it's compatible to the old system and only some - very special - projects might break. Anyway I would recommended to bundle these changes only with the next minor/major release and not inside a patch release. |
Yes, I agree with this concern. We have this rather old ticket, but still relevant nowadays #8163. In short, would be nice to include an automatic formatting step into all pull requests automation. |
aka a (bit early) Christmas present from XDEV to Vaadin 🎄
Description
This PR reworks the Vaadin Maven Plugin to improve performance.
The changes primarily target the
prepare-
andbuild-frontend
goals (all other goals are irrelevant for us) and are based on some experimental changes we have done in internal projects.Fixes #19596
Fixes #20359
Overview of the changes:
If you combine this situation with frequent (re)builds due to e.g. not a good working IDEs, this results in further problems like increased coffee consumption in between builds.
The improvement here is to only scan Vaadin dependencies by default.
The user can further customize and include/exclude certain dependencies if they want.
org.codehaus.plexus.build.BuildContext
optional (not everyone uses Eclipse ;) )Example configuration
In a example project (with ~40k LoC and ~150 dependencies) with above configuration we now have the following times:
prepare-frontend
build-frontend
Type of change
Checklist
Fixed existing tests so that they work. Feel free to add more tests if you like.
There seem to be some tests that don't work locally, e.g.:
[main] ERROR com.vaadin.flow.server.frontend.FrontendToolsLocator - Failed to execute the command '[C:\Program Files\maven\apache-maven-3.9.8\bin\mvn, -v]' java.io.IOException: Cannot run program "C:\Program Files\maven\apache-maven-3.9.8\bin\mvn": CreateProcess error=193, %1 ist keine zulässige Win32-Anwendung
orDefaultInstantiatorI18NTest.translationFileOnClasspath_instantiateDefaultI18N:122 expected:<[Default lang]> but was:<[deutsch]>
Additional for
Feature
type of changePS: