1414package org .eclipse .m2e .jdt .internal .launch ;
1515
1616import java .io .File ;
17+ import java .lang .reflect .InvocationTargetException ;
18+ import java .lang .reflect .Method ;
1719import java .util .ArrayList ;
1820import java .util .Arrays ;
21+ import java .util .Collection ;
1922import java .util .Collections ;
2023import java .util .HashSet ;
2124import java .util .LinkedHashSet ;
2629import org .slf4j .Logger ;
2730import org .slf4j .LoggerFactory ;
2831
32+ import org .eclipse .aether .util .version .GenericVersionScheme ;
33+ import org .eclipse .aether .version .InvalidVersionSpecificationException ;
34+ import org .eclipse .aether .version .Version ;
2935import org .eclipse .core .resources .IProject ;
3036import org .eclipse .core .resources .IWorkspaceRoot ;
3137import org .eclipse .core .resources .ResourcesPlugin ;
4955import org .eclipse .jdt .launching .StandardClasspathProvider ;
5056
5157import org .apache .maven .artifact .Artifact ;
58+ import org .apache .maven .plugin .Mojo ;
59+ import org .apache .maven .plugin .MojoExecution ;
5260import org .apache .maven .project .MavenProject ;
5361
5462import org .eclipse .m2e .core .MavenPlugin ;
@@ -101,12 +109,30 @@ public class MavenRuntimeClasspathProvider extends StandardClasspathProvider {
101109
102110 private static final String PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES = "m2e.disableAddMissingJUnit5ExecutionDependencies" ;
103111
112+ private static final String GROUP_ID_SUREFIRE_PLUGIN = "org.apache.maven.plugins" ; //$NON-NLS-1$
113+
114+ private static final String ARTIFACT_ID_SUREFIRE_PLUGIN = "maven-surefire-plugin" ; //$NON-NLS-1$
115+
116+ /**
117+ * Minimum Surefire version to support adding provider dependencies
118+ * (https://issues.apache.org/jira/browse/SUREFIRE-1564)
119+ */
120+ private static final Version MIN_SUREFIRE_VERSION ; //$NON-NLS-1$
121+
122+ private static final String GOAL_TEST = "test" ; //$NON-NLS-1$
123+
104124 private static final Set <String > supportedTypes = new HashSet <>();
105125 static {
106126 // not exactly nice, but works with eclipse 3.2, 3.3 and 3.4M3
107127 supportedTypes .add (MavenRuntimeClasspathProvider .JDT_JAVA_APPLICATION );
108128 supportedTypes .add (MavenRuntimeClasspathProvider .JDT_JUNIT_TEST );
109129 supportedTypes .add (MavenRuntimeClasspathProvider .JDT_TESTNG_TEST );
130+
131+ try {
132+ MIN_SUREFIRE_VERSION = new GenericVersionScheme ().parseVersion ("2.22.1" );
133+ } catch (InvalidVersionSpecificationException ex ) {
134+ throw new IllegalArgumentException ("Could not parse hardcoded version 2.22.1" , ex );
135+ }
110136 }
111137
112138 IMavenProjectRegistry projectManager = MavenPlugin .getMavenProjectRegistry ();
@@ -167,11 +193,11 @@ public IRuntimeClasspathEntry[] resolveClasspath(final IRuntimeClasspathEntry[]
167193 } else {
168194 context = projectFacade .createExecutionContext ();
169195 }
170- return context .execute ((ctx , monitor1 ) -> resolveClasspath0 (entries , configuration , monitor1 ), monitor );
196+ return context .execute ((ctx , monitor1 ) -> resolveClasspath0 (entries , configuration , monitor1 , ctx ), monitor );
171197 }
172198
173199 IRuntimeClasspathEntry [] resolveClasspath0 (IRuntimeClasspathEntry [] entries , ILaunchConfiguration configuration ,
174- IProgressMonitor monitor ) throws CoreException {
200+ IProgressMonitor monitor , IMavenExecutionContext context ) throws CoreException {
175201 IJavaProject javaProject = JavaRuntime .getJavaProject (configuration );
176202
177203 boolean isModularConfiguration = JavaRuntime .isModularConfiguration (configuration );
@@ -184,7 +210,7 @@ IRuntimeClasspathEntry[] resolveClasspath0(IRuntimeClasspathEntry[] entries, ILa
184210 for (IRuntimeClasspathEntry entry : entries ) {
185211 if (entry .getType () == IRuntimeClasspathEntry .CONTAINER
186212 && MavenClasspathHelpers .isMaven2ClasspathContainer (entry .getPath ())) {
187- addMavenClasspathEntries (all , entry , configuration , scope , monitor , isModularConfiguration );
213+ addMavenClasspathEntries (all , entry , configuration , scope , monitor , isModularConfiguration , context );
188214 } else if (entry .getType () == IRuntimeClasspathEntry .PROJECT ) {
189215 if (javaProject .getPath ().equals (entry .getPath ())) {
190216 addProjectEntries (all , entry .getPath (), scope , THIS_PROJECT_CLASSIFIER , configuration , monitor ,
@@ -207,7 +233,7 @@ private void addStandardClasspathEntries(Set<IRuntimeClasspathEntry> all, IRunti
207233
208234 private void addMavenClasspathEntries (Set <IRuntimeClasspathEntry > resolved ,
209235 IRuntimeClasspathEntry runtimeClasspathEntry , ILaunchConfiguration configuration , int scope ,
210- IProgressMonitor monitor , boolean isModularConfiguration ) throws CoreException {
236+ IProgressMonitor monitor , boolean isModularConfiguration , IMavenExecutionContext context ) throws CoreException {
211237 IJavaProject javaProject = JavaRuntime .getJavaProject (configuration );
212238 MavenJdtPlugin plugin = MavenJdtPlugin .getDefault ();
213239 IClasspathManager buildpathManager = plugin .getBuildpathManager ();
@@ -229,12 +255,128 @@ private void addMavenClasspathEntries(Set<IRuntimeClasspathEntry> resolved,
229255 }
230256 }
231257
232- if (scope == IClasspathManager .CLASSPATH_TEST && TESTKIND_ORG_ECLIPSE_JDT_JUNIT_LOADER_JUNIT5
258+ if (scope == IClasspathManager .CLASSPATH_TEST ) {
259+ // TODO: distinguish between junit and testng?
260+ addMavenSurefirePluginProviderDependencies (resolved , configuration , monitor , context , javaProject );
261+ }
262+ }
263+
264+ /**
265+ * Add Maven Surefire Plugin dependencies to the classpath which are required for the selected provider
266+ *
267+ * @param classpath
268+ * @param facade
269+ * @param maven
270+ * @param monitor
271+ * @throws CoreException
272+ */
273+ private void addMavenSurefirePluginProviderDependencies (Set <IRuntimeClasspathEntry > resolved ,
274+ ILaunchConfiguration configuration , IProgressMonitor monitor , IMavenExecutionContext context ,
275+ IJavaProject javaProject ) throws CoreException {
276+
277+ IMavenProjectFacade projectFacade = projectManager .create (javaProject .getProject (), monitor );
278+ if (Boolean .parseBoolean (projectFacade .getMavenProject ().getProperties ()
279+ .getProperty (PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES , "false" ))) { //$NON-NLS-1$
280+ log .debug ("Skipping adding Maven Surefire Plugin dependencies as property {} is set to true" ,
281+ PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES );
282+ return ;
283+ }
284+
285+ boolean surefireDependenciesAdded = false ;
286+ for (MojoExecution mojoExecution : projectFacade .getMojoExecutions (GROUP_ID_SUREFIRE_PLUGIN ,
287+ ARTIFACT_ID_SUREFIRE_PLUGIN , monitor , GOAL_TEST )) {
288+ // only add dependencies if the surefire plugin version is above 2.22.1 (https://issues.apache.org/jira/browse/SUREFIRE-1564)
289+ try {
290+ Version version = new GenericVersionScheme ().parseVersion (mojoExecution .getPlugin ().getVersion ());
291+ if (version .compareTo (MIN_SUREFIRE_VERSION ) < 0 ) {
292+ log .debug (
293+ "Skipping adding Maven Surefire Plugin dependencies for MojoExecution id {} of plugin {} as its version {} is below 2.22.1" ,
294+ mojoExecution .getExecutionId (), mojoExecution .getPlugin ().getId (),
295+ mojoExecution .getPlugin ().getVersion ());
296+ } else {
297+ log .debug ("Adding Maven Surefire Plugin dependencies for MojoExecution id {} of plugin {}" ,
298+ mojoExecution .getExecutionId (), mojoExecution .getPlugin ().getId ());
299+ addMavenSurefirePluginProviderDependencies (resolved , mojoExecution , context , monitor );
300+ surefireDependenciesAdded = true ;
301+ }
302+ } catch (InvalidVersionSpecificationException ex ) {
303+ log .warn ("Could not parse Maven Surefire Plugin version " + mojoExecution .getPlugin ().getVersion ()
304+ + " for MojoExecution id " + mojoExecution .getExecutionId () + ", skipping adding its provider dependencies" ,
305+ ex );
306+ }
307+
308+ }
309+
310+ // use legacy mechanism to add junit5 dependencies for junit5 launches when surefire plugin is not used or below 2.22.1
311+ if (!surefireDependenciesAdded && TESTKIND_ORG_ECLIPSE_JDT_JUNIT_LOADER_JUNIT5
233312 .equals (configuration .getAttribute (ATTRIBUTE_ORG_ECLIPSE_JDT_JUNIT_TEST_KIND , "" ))) {
313+ log .debug (
314+ "Adding missing JUnit5 execution dependencies for JUnit5 launch configuration via legacy method as no suitable Maven Surefire Plugin execution was found" );
234315 addMissingJUnit5ExecutionDependencies (resolved , monitor , javaProject );
235316 }
236317 }
237318
319+ @ SuppressWarnings ("unchecked" )
320+ Boolean addMavenSurefirePluginProviderDependencies (Set <IRuntimeClasspathEntry > resolved , MojoExecution mojoExecution ,
321+ IMavenExecutionContext context , IProgressMonitor monitor ) throws CoreException {
322+ Mojo mojo = MavenPlugin .getMaven ().getConfiguredMojo (context .getSession (), mojoExecution , Mojo .class );
323+ final Class <?> abstractSurefireMojoClass ;
324+ try {
325+ abstractSurefireMojoClass = Class .forName ("org.apache.maven.plugin.surefire.AbstractSurefireMojo" , false ,
326+ mojoExecution .getMojoDescriptor ().getRealm ());
327+ } catch (ClassNotFoundException ex ) {
328+ throw new IllegalStateException ("Could not load AbstractSurefireMojo class from surefire plugin realm" , ex );
329+ }
330+ Boolean isClasspathModified = false ;
331+ try {
332+ // look for surefire test classpath: https://github.com/apache/maven-surefire/blob/18f0c9f9dd850f98377032693fa5ec460f42b65b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1156C34-L1156C49
333+ Object testClassPath ;
334+ try {
335+ // available since Surefire 2.22.1 (https://github.com/apache/maven-surefire/commit/242c0e8a70be5a2a839ab00062c645f0d7b81137)
336+ Method generateTestClasspathMethod = abstractSurefireMojoClass .getDeclaredMethod ("generateTestClasspath" );
337+ generateTestClasspathMethod .setAccessible (true );
338+ testClassPath = generateTestClasspathMethod .invoke (mojo );
339+ } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e ) {
340+ log .warn ("Could not access generateTestClasspath() method on AbstractSurefireMojo" , e );
341+ return false ;
342+ }
343+ // and determine the providers used by this project with the given test classpath: https://github.com/apache/maven-surefire/blob/18f0c9f9dd850f98377032693fa5ec460f42b65b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1156
344+ try {
345+ Class <?> providerInfoClass = Class .forName ("org.apache.maven.surefire.providerapi.ProviderInfo" , false ,
346+ mojoExecution .getMojoDescriptor ().getRealm ());
347+ Class <?> testClassPathClass = Class .forName ("org.apache.maven.plugin.surefire.TestClassPath" , false ,
348+ mojoExecution .getMojoDescriptor ().getRealm ());
349+ Method getProviderNameMethod = providerInfoClass .getDeclaredMethod ("getProviderName" );
350+ Method getProviderClasspathMethod = providerInfoClass .getDeclaredMethod ("getProviderClasspath" );
351+ Method createProvidersMethod = abstractSurefireMojoClass .getDeclaredMethod ("createProviders" ,
352+ testClassPathClass );
353+ createProvidersMethod .setAccessible (true );
354+ Collection <?> providerInfos = (Collection <?>) createProvidersMethod .invoke (mojo , testClassPath );
355+ for (Object providerInfo : providerInfos ) {
356+ String providerName = (String ) getProviderNameMethod .invoke (providerInfo );
357+ log .debug ("Adding Surefire dependencies for provider: {}" , providerName );
358+ for (Artifact artifact : ((Collection <Artifact >) getProviderClasspathMethod .invoke (providerInfo ))) {
359+ // all returned artifacts are already resolved via the call to "getProviderClasspath()"
360+ File artifactFile = artifact .getFile ();
361+ resolved .add (JavaRuntime .newArchiveRuntimeClasspathEntry (IPath .fromOSString (artifactFile .getAbsolutePath ()),
362+ IRuntimeClasspathEntry .USER_CLASSES ));
363+ log .debug ("Added Surefire provider dependency: {}:{}:{} for provider {}" , artifact .getGroupId (),
364+ artifact .getArtifactId (), artifact .getVersion (), providerName );
365+ isClasspathModified = true ;
366+ }
367+ }
368+ } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException
369+ | ClassNotFoundException e ) {
370+ log .warn (
371+ "Could not access createProviders(org.apache.maven.plugin.surefire.TestClassPath) method on AbstractSurefireMojo" ,
372+ e );
373+ }
374+ } finally {
375+ MavenPlugin .getMaven ().releaseMojo (mojo , mojoExecution );
376+ }
377+ return isClasspathModified ;
378+ }
379+
238380 private void addMissingJUnit5ExecutionDependencies (Set <IRuntimeClasspathEntry > resolved , IProgressMonitor monitor ,
239381 IJavaProject javaProject ) throws CoreException {
240382 IMavenProjectFacade facade = projectManager .create (javaProject .getProject (), monitor );
0 commit comments