diff --git a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java index 643bef5c7bd..ce93eb22c24 100644 --- a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java +++ b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -210,15 +211,37 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { org.apache.maven.plugin.Mojo task = reflector.createMojo(this); findExecuteMethod(task.getClass()).invoke(task); + reflector.logIncompatibilities(getLog()::debug); } catch (MojoExecutionException | MojoFailureException e) { + logTroubleshootingHints(reflector, e); throw e; } catch (Exception e) { + logTroubleshootingHints(reflector, e); throw new MojoFailureException(e.getMessage(), e); } finally { Thread.currentThread().setContextClassLoader(tccl); } } + private void logTroubleshootingHints(Reflector reflector, Throwable ex) { + reflector.logIncompatibilities(getLog()::warn); + if (ex instanceof InvocationTargetException) { + ex = ex.getCause(); + } + StringBuilder errorMessage = new StringBuilder(ex.getMessage()); + Throwable cause = ex.getCause(); + while (cause != null) { + if (cause.getMessage() != null) { + errorMessage.append(" ").append(cause.getMessage()); + } + cause = cause.getCause(); + } + getLog().error( + "The build process encountered an error: " + errorMessage); + logError( + "To diagnose the issue, please re-run Maven with the -X option to enable detailed debug logging and identify the root cause."); + } + public void executeInternal() throws MojoFailureException { long start = System.nanoTime(); diff --git a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java index 2c79ea85c9a..66d956c2977 100644 --- a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java +++ b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -52,8 +53,13 @@ public final class Reflector { private static final Set DEPENDENCIES_GROUP_EXCLUSIONS = Set.of( "org.apache.maven", "org.codehaus.plexus", "org.slf4j", "org.eclipse.sisu"); + // Dependency required by the plugin but not provided by Flow at runtime + private static final Set REQUIRED_PLUGIN_DEPENDENCIES = Set.of( + "org.reflections:reflections:jar", + "org.zeroturnaround:zt-exec:jar"); private final URLClassLoader isolatedClassLoader; + private List dependenciesIncompatibility; private Object classFinder; /** @@ -66,9 +72,11 @@ public Reflector(URLClassLoader isolatedClassLoader) { this.isolatedClassLoader = isolatedClassLoader; } - private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { + private Reflector(URLClassLoader isolatedClassLoader, Object classFinder, + List dependenciesIncompatibility) { this.isolatedClassLoader = isolatedClassLoader; this.classFinder = classFinder; + this.dependenciesIncompatibility = dependenciesIncompatibility; } /** @@ -91,6 +99,7 @@ private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { * it is not possible to make a copy for it due to class * definition incompatibilities. */ + @SuppressWarnings("unchecked") static Reflector adapt(Object reflector) { if (reflector instanceof Reflector sameClassLoader) { return sameClassLoader; @@ -103,9 +112,13 @@ static Reflector adapt(Object reflector) { findField(reflectorClass, "isolatedClassLoader"), URLClassLoader.class); + List dependenciesIncompatibility = (List) ReflectTools + .getJavaFieldValue(reflector, findField(reflectorClass, + "dependenciesIncompatibility")); Object classFinder = ReflectTools.getJavaFieldValue(reflector, findField(reflectorClass, "classFinder")); - return new Reflector(classLoader, classFinder); + return new Reflector(classLoader, classFinder, + dependenciesIncompatibility); } catch (Exception e) { throw new IllegalArgumentException( "Object of type " + reflector.getClass().getName() @@ -198,9 +211,27 @@ public Mojo createMojo(BuildDevBundleMojo sourceMojo) throws Exception { */ public static Reflector of(MavenProject project, MojoExecution mojoExecution) { + List dependenciesIncompatibility = new ArrayList<>(); URLClassLoader classLoader = createIsolatedClassLoader(project, - mojoExecution); - return new Reflector(classLoader); + mojoExecution, dependenciesIncompatibility); + Reflector reflector = new Reflector(classLoader); + reflector.dependenciesIncompatibility = dependenciesIncompatibility; + return reflector; + } + + void logIncompatibilities(Consumer logger) { + if (dependenciesIncompatibility != null) { + logger.accept( + """ + Found dependencies defined with different versions in project and Vaadin maven plugin. + Project dependencies are used, but plugin execution could fail if the versions are incompatible. + In case of build failure please analyze the project dependencies and update versions or configure exclusions for potential offending transitive dependencies. + You can use 'mvn dependency:tree -Dincludes=groupId:artifactId' to detect where the dependency is defined in the project. + + """ + + String.join(System.lineSeparator(), + dependenciesIncompatibility)); + } } private synchronized Object getOrCreateClassFinder() throws Exception { @@ -215,7 +246,8 @@ private synchronized Object getOrCreateClassFinder() throws Exception { } private static URLClassLoader createIsolatedClassLoader( - MavenProject project, MojoExecution mojoExecution) { + MavenProject project, MojoExecution mojoExecution, + List dependenciesIncompatibility) { List urls = new ArrayList<>(); String outputDirectory = project.getBuild().getOutputDirectory(); if (outputDirectory != null) { @@ -246,17 +278,61 @@ private static URLClassLoader createIsolatedClassLoader( && artifact.getFile().getPath().matches( INCLUDE_FROM_COMPILE_DEPS_REGEX)))) .collect(Collectors.toMap(keyMapper, Function.identity()))); + if (mojoExecution != null) { - mojoExecution.getMojoDescriptor().getPluginDescriptor() - .getArtifacts().stream() + + List pluginDependencies = mojoExecution + .getMojoDescriptor().getPluginDescriptor().getArtifacts() + .stream() // Exclude all maven artifacts to prevent class loading // clash with maven.api class realm .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS .contains(artifact.getGroupId())) - .filter(artifact -> !projectDependencies - .containsKey(keyMapper.apply(artifact))) - .forEach(artifact -> projectDependencies - .put(keyMapper.apply(artifact), artifact)); + .toList(); + + // Exclude project artifact that are also defined as mandatory + // plugin dependencies. The version provided by the plugin will be + // used to prevent failures during maven build. + pluginDependencies.stream().map(keyMapper) + .filter(REQUIRED_PLUGIN_DEPENDENCIES::contains) + .forEach(projectDependencies::remove); + + // Preserve required plugin dependency that are not provided by Flow + // -1: dependency defined on both plugin and project, with different + // version + // 0: dependency defined on both plugin and project, with same + // version + // 1: dependency defined by the plugin only + Map> potentialDuplicates = pluginDependencies + .stream().collect(Collectors.groupingBy(pluginArtifact -> { + Artifact projectArtifact = projectDependencies + .get(keyMapper.apply(pluginArtifact)); + if (projectArtifact == null) { + return 1; + } else if (projectArtifact.getId() + .equals(pluginArtifact.getId())) { + return 0; + } + return -1; + })); + // Log potential plugin and project dependency versions + // incompatibilities. + if (potentialDuplicates.containsKey(-1)) { + potentialDuplicates.get(-1).stream().map(pluginArtifact -> { + String key = keyMapper.apply(pluginArtifact); + return String.format( + "%s: project version [%s], plugin version [%s]", + key, projectDependencies.get(key).getBaseVersion(), + pluginArtifact.getBaseVersion()); + }).forEach(dependenciesIncompatibility::add); + } + + // Add dependencies defined only by the plugin + if (potentialDuplicates.containsKey(1)) { + potentialDuplicates.get(1) + .forEach(artifact -> projectDependencies + .put(keyMapper.apply(artifact), artifact)); + } } projectDependencies.values().stream() diff --git a/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/invoker.properties new file mode 100644 index 00000000000..e0a3360d88b --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/invoker.properties @@ -0,0 +1,18 @@ +# +# Copyright 2000-2024 Vaadin Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +invoker.goals=clean package +invoker.buildResult=failure \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/pom.xml b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/pom.xml new file mode 100644 index 00000000000..12ddc3f2910 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + com.vaadin.test.maven + offending-dependency-project + 1.0 + jar + + + Tests that project dependencies does not override plugin required dependency. + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + 3.9.9 + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + + commons-io + commons-io + 2.6 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java new file mode 100644 index 00000000000..fd5304d4b88 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java @@ -0,0 +1,20 @@ +package com.vaadin.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +/** + * Hello world! + */ +public class ProjectFlowExtension implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + System.out.println("ProjectFlowExtension"); + bootstrapTypeScript.add("(window as any).testProject=1;"); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/verify.bsh b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/verify.bsh new file mode 100644 index 00000000000..331b2b436f0 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/offending-dependency-project/verify.bsh @@ -0,0 +1,15 @@ +import java.nio.file.*; + +flowTsx = basedir.toPath().resolve("build.log"); +if ( !Files.exists(flowTsx, new LinkOption[0]) ) +{ + throw new RuntimeException("build.log not found"); +} + +lines = Files.readString(flowTsx); +if ( + !lines.contains("Found dependencies defined with different versions in project and Vaadin maven plugin") && + !lines.matches("^commons-io:commons-io.*\\[2\\.6\\],.*") + ) { + throw new RuntimeException("Offending commons-io 2.6 dependency not detected"); +} diff --git a/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/invoker.properties new file mode 100644 index 00000000000..5aa13827263 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/invoker.properties @@ -0,0 +1,17 @@ +# +# Copyright 2000-2024 Vaadin Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +invoker.goals=clean package diff --git a/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/pom.xml b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/pom.xml new file mode 100644 index 00000000000..32d79b0e917 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.vaadin.test.maven + plugin-pinned-deps-project + 1.0 + jar + + + Tests that project dependencies does not override plugin required dependency. + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + 3.9.9 + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + org.reflections + reflections + 0.9.10 + + + org.zeroturnaround + zt-exec + 1.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java new file mode 100644 index 00000000000..fd5304d4b88 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/plugin-pinned-deps-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java @@ -0,0 +1,20 @@ +package com.vaadin.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +/** + * Hello world! + */ +public class ProjectFlowExtension implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + System.out.println("ProjectFlowExtension"); + bootstrapTypeScript.add("(window as any).testProject=1;"); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java index f1367cf19d8..67608e4e6f5 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java @@ -18,6 +18,7 @@ import javax.inject.Inject; import java.io.File; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -52,6 +53,7 @@ import com.vaadin.flow.plugin.base.BuildFrontendUtil; import com.vaadin.flow.plugin.base.PluginAdapterBase; import com.vaadin.flow.server.Constants; +import com.vaadin.flow.server.ExecutionFailedException; import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.frontend.FrontendTools; import com.vaadin.flow.server.frontend.FrontendUtils; @@ -305,15 +307,37 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { Mojo task = reflector.createMojo(this); findExecuteMethod(task.getClass()).invoke(task); + reflector.logIncompatibilities(getLog()::debug); } catch (MojoExecutionException | MojoFailureException e) { + logTroubleshootingHints(reflector, e); throw e; } catch (Exception e) { + logTroubleshootingHints(reflector, e); throw new MojoFailureException(e.getMessage(), e); } finally { Thread.currentThread().setContextClassLoader(tccl); } } + private void logTroubleshootingHints(Reflector reflector, Throwable ex) { + reflector.logIncompatibilities(getLog()::warn); + if (ex instanceof InvocationTargetException) { + ex = ex.getCause(); + } + StringBuilder errorMessage = new StringBuilder(ex.getMessage()); + Throwable cause = ex.getCause(); + while (cause != null) { + if (cause.getMessage() != null) { + errorMessage.append(" ").append(cause.getMessage()); + } + cause = cause.getCause(); + } + getLog().error( + "The build process encountered an error: " + errorMessage); + logError( + "To diagnose the issue, please re-run Maven with the -X option to enable detailed debug logging and identify the root cause."); + } + /** * Perform whatever build-process behavior this Mojo * implements.
@@ -691,10 +715,10 @@ private void checkFlowCompatibility(PluginDescriptor pluginDescriptor) { .equals(artifact.getGroupId()) && "flow-server".equals(artifact.getArtifactId()); String projectFlowVersion = project.getArtifacts().stream() - .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .filter(isFlowServer).map(Artifact::getBaseVersion).findFirst() .orElse(null); String pluginFlowVersion = pluginDescriptor.getArtifacts().stream() - .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .filter(isFlowServer).map(Artifact::getBaseVersion).findFirst() .orElse(null); if (!Objects.equals(projectFlowVersion, pluginFlowVersion)) { getLog().warn( diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java index 175ead2fe27..e5e2e67ca41 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -52,8 +53,13 @@ public final class Reflector { private static final Set DEPENDENCIES_GROUP_EXCLUSIONS = Set.of( "org.apache.maven", "org.codehaus.plexus", "org.slf4j", "org.eclipse.sisu"); + // Dependency required by the plugin but not provided by Flow at runtime + private static final Set REQUIRED_PLUGIN_DEPENDENCIES = Set.of( + "org.reflections:reflections:jar", + "org.zeroturnaround:zt-exec:jar"); private final URLClassLoader isolatedClassLoader; + private List dependenciesIncompatibility; private Object classFinder; /** @@ -66,9 +72,11 @@ public Reflector(URLClassLoader isolatedClassLoader) { this.isolatedClassLoader = isolatedClassLoader; } - private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { + private Reflector(URLClassLoader isolatedClassLoader, Object classFinder, + List dependenciesIncompatibility) { this.isolatedClassLoader = isolatedClassLoader; this.classFinder = classFinder; + this.dependenciesIncompatibility = dependenciesIncompatibility; } /** @@ -91,6 +99,7 @@ private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { * it is not possible to make a copy for it due to class * definition incompatibilities. */ + @SuppressWarnings("unchecked") static Reflector adapt(Object reflector) { if (reflector instanceof Reflector sameClassLoader) { return sameClassLoader; @@ -103,9 +112,13 @@ static Reflector adapt(Object reflector) { findField(reflectorClass, "isolatedClassLoader"), URLClassLoader.class); + List dependenciesIncompatibility = (List) ReflectTools + .getJavaFieldValue(reflector, findField(reflectorClass, + "dependenciesIncompatibility")); Object classFinder = ReflectTools.getJavaFieldValue(reflector, findField(reflectorClass, "classFinder")); - return new Reflector(classLoader, classFinder); + return new Reflector(classLoader, classFinder, + dependenciesIncompatibility); } catch (Exception e) { throw new IllegalArgumentException( "Object of type " + reflector.getClass().getName() @@ -198,9 +211,27 @@ public Mojo createMojo(FlowModeAbstractMojo sourceMojo) throws Exception { */ public static Reflector of(MavenProject project, MojoExecution mojoExecution) { + List dependenciesIncompatibility = new ArrayList<>(); URLClassLoader classLoader = createIsolatedClassLoader(project, - mojoExecution); - return new Reflector(classLoader); + mojoExecution, dependenciesIncompatibility); + Reflector reflector = new Reflector(classLoader); + reflector.dependenciesIncompatibility = dependenciesIncompatibility; + return reflector; + } + + void logIncompatibilities(Consumer logger) { + if (dependenciesIncompatibility != null) { + logger.accept( + """ + Found dependencies defined with different versions in project and Vaadin maven plugin. + Project dependencies are used, but plugin execution could fail if the versions are incompatible. + In case of build failure please analyze the project dependencies and update versions or configure exclusions for potential offending transitive dependencies. + You can use 'mvn dependency:tree -Dincludes=groupId:artifactId' to detect where the dependency is defined in the project. + + """ + + String.join(System.lineSeparator(), + dependenciesIncompatibility)); + } } private synchronized Object getOrCreateClassFinder() throws Exception { @@ -215,7 +246,8 @@ private synchronized Object getOrCreateClassFinder() throws Exception { } private static URLClassLoader createIsolatedClassLoader( - MavenProject project, MojoExecution mojoExecution) { + MavenProject project, MojoExecution mojoExecution, + List dependenciesIncompatibility) { List urls = new ArrayList<>(); String outputDirectory = project.getBuild().getOutputDirectory(); if (outputDirectory != null) { @@ -246,17 +278,61 @@ private static URLClassLoader createIsolatedClassLoader( && artifact.getFile().getPath().matches( INCLUDE_FROM_COMPILE_DEPS_REGEX)))) .collect(Collectors.toMap(keyMapper, Function.identity()))); + if (mojoExecution != null) { - mojoExecution.getMojoDescriptor().getPluginDescriptor() - .getArtifacts().stream() + + List pluginDependencies = mojoExecution + .getMojoDescriptor().getPluginDescriptor().getArtifacts() + .stream() // Exclude all maven artifacts to prevent class loading // clash with maven.api class realm .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS .contains(artifact.getGroupId())) - .filter(artifact -> !projectDependencies - .containsKey(keyMapper.apply(artifact))) - .forEach(artifact -> projectDependencies - .put(keyMapper.apply(artifact), artifact)); + .toList(); + + // Exclude project artifact that are also defined as mandatory + // plugin dependencies. The version provided by the plugin will be + // used to prevent failures during maven build. + pluginDependencies.stream().map(keyMapper) + .filter(REQUIRED_PLUGIN_DEPENDENCIES::contains) + .forEach(projectDependencies::remove); + + // Preserve required plugin dependency that are not provided by Flow + // -1: dependency defined on both plugin and project, with different + // version + // 0: dependency defined on both plugin and project, with same + // version + // 1: dependency defined by the plugin only + Map> potentialDuplicates = pluginDependencies + .stream().collect(Collectors.groupingBy(pluginArtifact -> { + Artifact projectArtifact = projectDependencies + .get(keyMapper.apply(pluginArtifact)); + if (projectArtifact == null) { + return 1; + } else if (projectArtifact.getId() + .equals(pluginArtifact.getId())) { + return 0; + } + return -1; + })); + // Log potential plugin and project dependency versions + // incompatibilities. + if (potentialDuplicates.containsKey(-1)) { + potentialDuplicates.get(-1).stream().map(pluginArtifact -> { + String key = keyMapper.apply(pluginArtifact); + return String.format( + "%s: project version [%s], plugin version [%s]", + key, projectDependencies.get(key).getBaseVersion(), + pluginArtifact.getBaseVersion()); + }).forEach(dependenciesIncompatibility::add); + } + + // Add dependencies defined only by the plugin + if (potentialDuplicates.containsKey(1)) { + potentialDuplicates.get(1) + .forEach(artifact -> projectDependencies + .put(keyMapper.apply(artifact), artifact)); + } } projectDependencies.values().stream() diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java index b8a1cfd80b4..a707320f3a5 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java @@ -178,6 +178,16 @@ public FrontendDependencies(ClassFinder finder, long ms = (System.nanoTime() - start) / 1000000; log().info("Visited {} classes. Took {} ms.", visitedClasses.size(), ms); + } catch (IllegalArgumentException ex) { + StackTraceElement[] stackTrace = ex.getStackTrace(); + if (ex.getMessage() != null + && ex.getMessage().startsWith("Unsupported api ") + && stackTrace.length > 0 && stackTrace[0].getClassName() + .equals("org.objectweb.asm.ClassVisitor")) { + log().error( + "Invalid asm library version. Please make sure that the project does not override org.ow2.asm:asm dependency defined by Vaadin with an incompatible version."); + } + throw ex; } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IOException e) { throw new IllegalStateException(