diff --git a/plugins/org.springframework.ide.eclipse.boot.launch.test/src/org/springframework/ide/eclipse/boot/launch/test/BootLaunchConfigurationDelegateTest.java b/plugins/org.springframework.ide.eclipse.boot.launch.test/src/org/springframework/ide/eclipse/boot/launch/test/BootLaunchConfigurationDelegateTest.java index af0e05be56..75dc5e3611 100644 --- a/plugins/org.springframework.ide.eclipse.boot.launch.test/src/org/springframework/ide/eclipse/boot/launch/test/BootLaunchConfigurationDelegateTest.java +++ b/plugins/org.springframework.ide.eclipse.boot.launch.test/src/org/springframework/ide/eclipse/boot/launch/test/BootLaunchConfigurationDelegateTest.java @@ -16,10 +16,13 @@ import java.net.URL; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.function.Predicate; +import javax.xml.transform.stream.StreamResult; + import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -38,6 +41,7 @@ import org.springframework.ide.eclipse.boot.launch.process.BootProcessFactory; import org.springframework.ide.eclipse.boot.test.util.LaunchResult; import org.springframework.ide.eclipse.boot.test.util.LaunchUtil; +import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition; import org.springsource.ide.eclipse.commons.frameworks.test.util.Timewatch; import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; @@ -372,6 +376,47 @@ private void doThinWrapperLaunchTest(File thinWrapper, String importStrategy) th } } + /** + * Name fragments that, when seen in a dependency, probably mean the dependency is a test dependency + */ + private static final String[] testFragments = { + "test", + "junit", + "assertj", + "mockito", + }; + + private static boolean isTestClasspathEntry(String cpe) { + for (String testFrag : testFragments) { + String normalizedEntry = cpe.replace('\\', '/'); + int slash = normalizedEntry.lastIndexOf('/'); + if (slash>=0) { + normalizedEntry = normalizedEntry.substring(slash); + } + if (normalizedEntry.contains(testFrag)) { + return true; + } + } + return false; + } + + + public void testRuntimeClasspathNoTestStuffGradle() throws Exception { + IProject project = projects.createBootProject("gradle-test-project", withImportStrategy("GRADLE-Buildship 3.x")); + ILaunchConfigurationWorkingCopy wc = createBaseWorkingCopy(project.getName(), "com.example.demo.GradleTestProjectApplication"); + String[] cp = getClasspath(new BootLaunchConfigurationDelegate(), wc); + + ArrayList testDependencies = new ArrayList<>(); + for (String cpe : cp) { + if (isTestClasspathEntry(cpe)) { + testDependencies.add(cpe); + } + } + if (!testDependencies.isEmpty()) { + fail("Shouldn't have test dependencies but found: "+testDependencies); + } + } + public void testRuntimeClasspathNoTestStuff() throws Exception { createLaunchReadyProject(TEST_PROJECT); ILaunchConfigurationWorkingCopy wc = createBaseWorkingCopy(); @@ -443,7 +488,8 @@ private static void assertClasspathHasEntry(String[] cp, String expect) { private String[] getClasspath(JavaLaunchDelegate delegate, ILaunchConfigurationWorkingCopy wc) throws CoreException { System.out.println("\n====classpath according to "+delegate.getClass().getSimpleName()); - String[] classpath = delegate.getClasspath(wc); + String[][] cpAndMp = delegate.getClasspathAndModulepath(wc); + String[] classpath = cpAndMp[0]; for (String element : classpath) { int chop = element.lastIndexOf('/'); System.out.println('"'+element.substring(chop+1)+"\","); diff --git a/plugins/org.springframework.ide.eclipse.boot.launch/src/org/springframework/ide/eclipse/boot/launch/AbstractBootLaunchConfigurationDelegate.java b/plugins/org.springframework.ide.eclipse.boot.launch/src/org/springframework/ide/eclipse/boot/launch/AbstractBootLaunchConfigurationDelegate.java index 3d05d93c33..549dbc3762 100644 --- a/plugins/org.springframework.ide.eclipse.boot.launch/src/org/springframework/ide/eclipse/boot/launch/AbstractBootLaunchConfigurationDelegate.java +++ b/plugins/org.springframework.ide.eclipse.boot.launch/src/org/springframework/ide/eclipse/boot/launch/AbstractBootLaunchConfigurationDelegate.java @@ -48,6 +48,8 @@ public abstract class AbstractBootLaunchConfigurationDelegate extends JavaLaunchDelegate { + private static final String JDT_JAVA_APPLICATION = "org.eclipse.jdt.launching.localJavaApplication"; + private static final String SILENT_EXIT_EXCEPTION = "org.springframework.boot.devtools.restart.SilentExitExceptionHandler$SilentExitException"; private static final String M2E_CLASSPATH_PROVIDER = "org.eclipse.m2e.launchconfig.classpathProvider"; @@ -59,6 +61,7 @@ public abstract class AbstractBootLaunchConfigurationDelegate extends JavaLaunch private static final String BOOT_MAVEN_SOURCE_PATH_PROVIDER = "org.springframework.ide.eclipse.boot.launch.BootMavenSourcePathProvider"; private static final String BOOT_MAVEN_CLASS_PATH_PROVIDER = "org.springframework.ide.eclipse.boot.launch.BootMavenClassPathProvider"; + private static final String BUILDSHIP_CLASS_PATH_PROVIDER = "org.eclipse.buildship.core.classpathprovider"; /** * Spring boot properties are stored as launch confiuration properties with @@ -369,17 +372,71 @@ protected ILaunchConfiguration configureClassPathProviders(ILaunchConfiguration IProject project = BootLaunchConfigurationDelegate.getProject(conf); if (project.hasNature(SpringBootCore.M2E_NATURE)) { conf = modify(conf, (ILaunchConfigurationWorkingCopy wc) -> { - enableClasspathProviders(wc); + enableMavenClasspathProviders(wc); + }); + } else if (project.hasNature(SpringBootCore.BUILDSHIP_NATURE)) { + conf = modify(conf, wc -> { + enableGradleClasspathProviders(wc); }); } return conf; } - public static void enableClasspathProviders(ILaunchConfigurationWorkingCopy wc) { + @Override + public String[][] getClasspathAndModulepath(ILaunchConfiguration configuration) throws CoreException { + if (configuration.hasAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE)) { + //TODO: This is a dirty hack. We 'trick' BuildShip to treat our launch config as if it is a plain JDT launch by making + // a temporary copy of it. + //A request to make BuildShip provide a cleaner way to do this was filed here: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=543328 + //If that bug is resolved this code should be removed. + configuration = BootLaunchConfigurationDelegate.copyAs(configuration, JDT_JAVA_APPLICATION); + } + return super.getClasspathAndModulepath(configuration); + } + + @Override + public String[] getClasspath(ILaunchConfiguration configuration) throws CoreException { + if (configuration.hasAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE)) { + //TODO: This is a dirty hack. We 'trick' BuildShip to treat our launch config as if it is a plain JDT launch by making + // a temporary copy of it. + //A request to make BuildShip provide a cleaner way to do this was filed here: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=543328 + //If that bug is resolved this code should be removed. + configuration = BootLaunchConfigurationDelegate.copyAs(configuration, JDT_JAVA_APPLICATION); + } + return super.getClasspath(configuration); + } + + @Override + public String[][] getBootpathExt(ILaunchConfiguration configuration) throws CoreException { + if (configuration.hasAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE)) { + //TODO: This is a dirty hack. We 'trick' BuildShip to treat our launch config as if it is a plain JDT launch by making + // a temporary copy of it. + //A request to make BuildShip provide a cleaner way to do this was filed here: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=543328 + //If that bug is resolved this code should be removed. + configuration = BootLaunchConfigurationDelegate.copyAs(configuration, JDT_JAVA_APPLICATION); + } + return super.getBootpathExt(configuration); + } + + public static void enableMavenClasspathProviders(ILaunchConfigurationWorkingCopy wc) { setAttribute(wc, IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER, BOOT_MAVEN_SOURCE_PATH_PROVIDER); setAttribute(wc, IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, BOOT_MAVEN_CLASS_PATH_PROVIDER); } + public static void enableGradleClasspathProviders(ILaunchConfigurationWorkingCopy wc) { + /* This is found in typical java launch config for buildship project. It plays a crucial role in + * computing correct runtime classpath: + * + * + * + */ + setAttribute(wc, IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, BUILDSHIP_CLASS_PATH_PROVIDER); + setAttribute(wc, IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, true); + } + private ILaunchConfiguration modify(ILaunchConfiguration conf, Consumer mutator) throws CoreException { ILaunchConfigurationWorkingCopy wc = conf.getWorkingCopy(); try { @@ -392,6 +449,16 @@ private ILaunchConfiguration modify(ILaunchConfiguration conf, Consumer