IDE support for TestJars: maven projects in the workspace only
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2023 Pivotal Software, Inc.
|
||||
* Copyright (c) 2019, 2024 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -16,11 +16,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4e.LanguageServers;
|
||||
import org.eclipse.lsp4e.LanguageServers.LanguageServerProjectExecutor;
|
||||
import org.eclipse.lsp4e.LanguageServersRegistry;
|
||||
import org.eclipse.lsp4j.ExecuteCommandOptions;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.springframework.ide.eclipse.boot.launch.BootLsCommandUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@@ -35,7 +32,7 @@ public final class DefaultLiveProcessCommandExecutor implements LiveProcessComma
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Flux<CommandInfo> listCommands() {
|
||||
List<CompletableFuture<List<CommandInfo>>> futures = getLanguageServers().computeAll(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
List<CompletableFuture<List<CommandInfo>>> futures = BootLsCommandUtils.getLanguageServers(CMD_LIST_PROCESSES).computeAll(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
CMD_LIST_PROCESSES,
|
||||
ImmutableList.of()
|
||||
)).thenApply(o -> {
|
||||
@@ -54,21 +51,10 @@ public final class DefaultLiveProcessCommandExecutor implements LiveProcessComma
|
||||
|
||||
@Override
|
||||
public Mono<Void> executeCommand(CommandInfo cmd) {
|
||||
return Mono.fromFuture(getLanguageServers().collectAll(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
return Mono.fromFuture(BootLsCommandUtils.getLanguageServers(cmd.command).collectAll(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
cmd.command,
|
||||
ImmutableList.of(cmd.info)
|
||||
))).thenAccept(null));
|
||||
))).thenAccept(l -> {}));
|
||||
}
|
||||
|
||||
private LanguageServerProjectExecutor getLanguageServers() {
|
||||
return LanguageServers.forProject(null).withFilter(cap -> {
|
||||
ExecuteCommandOptions commandCap = cap.getExecuteCommandProvider();
|
||||
if (commandCap!=null) {
|
||||
List<String> supportedCommands = commandCap.getCommands();
|
||||
return supportedCommands!=null && supportedCommands.contains(CMD_LIST_PROCESSES);
|
||||
}
|
||||
return false;
|
||||
}).withPreferredServer(LanguageServersRegistry.getInstance()
|
||||
.getDefinition("org.eclipse.languageserver.languages.springboot"));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
|
||||
@@ -29,7 +29,11 @@ Require-Bundle: org.eclipse.ui,
|
||||
org.eclipse.jface.text,
|
||||
org.eclipse.jdt.ui,
|
||||
org.eclipse.ui.genericeditor,
|
||||
org.eclipse.ui.ide
|
||||
org.eclipse.ui.ide,
|
||||
org.eclipse.lsp4e,
|
||||
org.eclipse.lsp4j,
|
||||
com.google.gson,
|
||||
org.eclipse.jdt.junit.core
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-17
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: org.springframework.ide.eclipse.boot.launch,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Pivotal, Inc.
|
||||
* Copyright (c) 2015, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -15,6 +15,7 @@ import org.eclipse.core.runtime.preferences.DefaultScope;
|
||||
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
|
||||
import org.eclipse.core.runtime.preferences.InstanceScope;
|
||||
import org.eclipse.debug.core.DebugPlugin;
|
||||
import org.eclipse.debug.core.ILaunchManager;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.Bundle;
|
||||
@@ -37,7 +38,8 @@ public class BootLaunchActivator extends AbstractUIPlugin {
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
super.start(context);
|
||||
workspaceListener = new BootLaunchConfDeleter(ResourcesPlugin.getWorkspace(), DebugPlugin.getDefault().getLaunchManager());
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
workspaceListener = new BootLaunchConfDeleter(ResourcesPlugin.getWorkspace(), launchManager);
|
||||
instance = this;
|
||||
|
||||
IPreferenceStore myStore = instance.getPreferenceStore();
|
||||
@@ -45,6 +47,8 @@ public class BootLaunchActivator extends AbstractUIPlugin {
|
||||
setPreference("org.eclipse.jdt.debug.ui", "org.eclipse.jdt.debug.ui.prompt_unable_to_install_breakpoint", false);
|
||||
myStore.setValue("cglib.breakpoint.warning.disabled", true);
|
||||
}
|
||||
|
||||
launchManager.addLaunchListener(TestJarLaunchListener.getSingletonInstance());
|
||||
}
|
||||
|
||||
private void setPreference(String plugin, String key, boolean value) {
|
||||
@@ -70,6 +74,8 @@ public class BootLaunchActivator extends AbstractUIPlugin {
|
||||
if (workspaceListener!=null) {
|
||||
workspaceListener.dispose();
|
||||
}
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
launchManager.removeLaunchListener(TestJarLaunchListener.getSingletonInstance());
|
||||
super.stop(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.launch;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4e.LanguageServers;
|
||||
import org.eclipse.lsp4e.LanguageServers.LanguageServerProjectExecutor;
|
||||
import org.eclipse.lsp4e.LanguageServersRegistry;
|
||||
import org.eclipse.lsp4j.ExecuteCommandOptions;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class BootLsCommandUtils {
|
||||
|
||||
private static Gson GSON = new Gson();
|
||||
|
||||
public static LanguageServerProjectExecutor getLanguageServers(String command) {
|
||||
return LanguageServers.forProject(null).withFilter(cap -> {
|
||||
ExecuteCommandOptions commandCap = cap.getExecuteCommandProvider();
|
||||
if (commandCap!=null) {
|
||||
List<String> supportedCommands = commandCap.getCommands();
|
||||
return supportedCommands!=null && supportedCommands.contains(command);
|
||||
}
|
||||
return false;
|
||||
}).withPreferredServer(LanguageServersRegistry.getInstance()
|
||||
.getDefinition("org.eclipse.languageserver.languages.springboot"));
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<Optional<T>> executeCommand(TypeToken<T> resType, String cmd, Object... params) {
|
||||
return getLanguageServers(cmd).computeFirst(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
cmd,
|
||||
ImmutableList.of(params)
|
||||
))).thenApply(o -> o.map(v -> GSON.fromJson(GSON.toJsonTree(v), resType)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.ILaunch;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
|
||||
import org.eclipse.debug.core.ILaunchDelegate;
|
||||
import org.eclipse.debug.core.ILaunchManager;
|
||||
import org.eclipse.debug.core.ILaunchesListener2;
|
||||
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
|
||||
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class TestJarLaunchListener implements ILaunchesListener2 {
|
||||
|
||||
private static TestJarLaunchListener instance;
|
||||
|
||||
private static final Pattern TESTJAR_PATTERN = Pattern.compile("^spring-boot-testjars-\\d+\\.\\d+\\.\\d+(.*)?.jar$");
|
||||
|
||||
private static final String TESTJAR_ARTIFACTS = "spring.boot.test-jar-artifacts";
|
||||
|
||||
public record ExecutableProject(String name, String uri, String gav, String mainClass, Collection<String> classpath) {}
|
||||
|
||||
public static TestJarLaunchListener getSingletonInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TestJarLaunchListener();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void launchRemoved(ILaunch launch) {
|
||||
clearTestJarWorkspaceProjectFiles(launch.getLaunchConfiguration());
|
||||
}
|
||||
|
||||
private void clearTestJarWorkspaceProjectFiles(ILaunchConfiguration configuration) {
|
||||
try {
|
||||
if (JUnitLaunchConfigurationConstants.ID_JUNIT_APPLICATION.equals(configuration.getType().getIdentifier())) {
|
||||
Map<String, String> oldTestJarArtifacts = configuration.getAttribute(TESTJAR_ARTIFACTS, Collections.emptyMap());
|
||||
Map<String, String> env = new HashMap<>(configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, Collections.emptyMap()));
|
||||
for (Map.Entry<String, String> testJarArtifactEnvEntry : oldTestJarArtifacts.entrySet()) {
|
||||
env.remove(testJarArtifactEnvEntry.getKey());
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(testJarArtifactEnvEntry.getValue()));
|
||||
} catch (IOException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
|
||||
wc.removeAttribute(TESTJAR_ARTIFACTS);
|
||||
if (env.isEmpty()) {
|
||||
wc.removeAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES);
|
||||
} else {
|
||||
wc.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env);
|
||||
}
|
||||
wc.doSave();
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupTestJarWorkspaceProjectFiles(ILaunchConfiguration configuration, Map<String, String> testJarArtifactsEnvMap) {
|
||||
try {
|
||||
Map<String, String> originalEnv = new HashMap<>(configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, Collections.emptyMap()));
|
||||
Map<String, String> oldTestJarArtifacts = configuration.getAttribute(TESTJAR_ARTIFACTS, Collections.emptyMap());
|
||||
for (Map.Entry<String, String> envEntry : oldTestJarArtifacts.entrySet()) {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(envEntry.getValue()));
|
||||
} catch (IOException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
originalEnv.remove(envEntry.getKey());
|
||||
}
|
||||
for (String envVar : originalEnv.keySet()) {
|
||||
testJarArtifactsEnvMap.remove(envVar);
|
||||
}
|
||||
originalEnv.putAll(testJarArtifactsEnvMap);
|
||||
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
|
||||
wc.setAttribute(TESTJAR_ARTIFACTS, testJarArtifactsEnvMap);
|
||||
wc.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, originalEnv);
|
||||
wc.doSave();
|
||||
} catch (CoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void launchAdded(ILaunch launch) {
|
||||
try {
|
||||
ILaunchConfiguration configuration = launch.getLaunchConfiguration();
|
||||
if (JUnitLaunchConfigurationConstants.ID_JUNIT_APPLICATION.equals(configuration.getType().getIdentifier())) {
|
||||
ILaunchDelegate[] delegates = configuration.getType().getDelegates(Set.of(launch.getLaunchMode()));
|
||||
if (delegates.length > 0) {
|
||||
JUnitLaunchConfigurationDelegate delegate = (JUnitLaunchConfigurationDelegate) delegates[0].getDelegate();
|
||||
String[] classpath = delegate.getClasspathAndModulepath(configuration)[0];
|
||||
if (isTestJarsOnClasspath(classpath)) {
|
||||
try {
|
||||
getTestJarArtifactsMap()
|
||||
.thenAccept(testJarArtifactsEnvMap -> setupTestJarWorkspaceProjectFiles(configuration, testJarArtifactsEnvMap))
|
||||
.get(3000000, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String createTestJarArtifactEnvKey(String gav) {
|
||||
return "TESTJARS_ARTIFACT_%s".formatted(gav.replace(":", "_"));
|
||||
}
|
||||
|
||||
private static boolean isTestJarsOnClasspath(String[] classpath) {
|
||||
for (String cpe : classpath) {
|
||||
Path p = Paths.get(cpe);
|
||||
if (Files.isRegularFile(p) && TESTJAR_PATTERN.matcher(p.getFileName().toString()).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CompletableFuture<Map<String, String>> getTestJarArtifactsMap() {
|
||||
try {
|
||||
Optional<?> opt = BootLsCommandUtils.executeCommand(TypeToken.getParameterized(List.class, ExecutableProject.class), "sts/spring-boot/executableBootProjects").get(3, TimeUnit.SECONDS);
|
||||
if (opt.isPresent() && opt.get() instanceof List<?> projects) {
|
||||
Map<String, String> gavToFile = new ConcurrentHashMap<>();
|
||||
CompletableFuture<?>[] futures = projects.stream().map(ExecutableProject.class::cast).map(p -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Path file = Files.createTempFile("%s_".formatted(p.gav().replace(":", "_")), UUID.randomUUID().toString());
|
||||
Files.write(file, List.of(
|
||||
"# the main class to invoke",
|
||||
"main=%s".formatted(p.mainClass()),
|
||||
"# the classpath to use delimited by the OS specific delimiters",
|
||||
"classpath=%s".formatted(String.join(File.pathSeparator, p.classpath())
|
||||
)));
|
||||
gavToFile.put(createTestJarArtifactEnvKey(p.gav()), file.toFile().toString());
|
||||
} catch (IOException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
})).toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(futures).thenApply(v -> gavToFile);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(Collections.emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchesRemoved(ILaunch[] launches) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchesAdded(ILaunch[] launches) {
|
||||
for (ILaunch l : launches) {
|
||||
launchAdded(l);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchesChanged(ILaunch[] launches) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchesTerminated(ILaunch[] launches) {
|
||||
for (ILaunch l : launches) {
|
||||
clearTestJarWorkspaceProjectFiles(l.getLaunchConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -35,7 +35,7 @@ public class GradleJavaProject extends LegacyJavaProject {
|
||||
private static final Logger log = LoggerFactory.getLogger(GradleJavaProject.class);
|
||||
|
||||
private GradleJavaProject(FileObserver fileObserver, Path projectDataCache, IClasspath classpath, File gradleFile, JavadocService javadocService) {
|
||||
super(fileObserver, gradleFile.getParentFile().toURI(), projectDataCache, classpath, javadocService, IProjectBuild.create(ProjectBuild.GRADLE_PROJECT_TYPE, gradleFile.toURI()));
|
||||
super(fileObserver, gradleFile.getParentFile().toURI(), projectDataCache, classpath, javadocService, IProjectBuild.create(ProjectBuild.GRADLE_PROJECT_TYPE, gradleFile.toURI(), null));
|
||||
}
|
||||
|
||||
public static GradleJavaProject create(FileObserver fileObserver, GradleCore gradle, File gradleFile, Path projectDataCache, JavadocService javadocService) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.java;
|
||||
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Gav;
|
||||
|
||||
public interface IGav {
|
||||
|
||||
String getGroupId();
|
||||
|
||||
String getArtifactId();
|
||||
|
||||
String getVersion();
|
||||
|
||||
static IGav create(Gav gav) {
|
||||
return new IGav() {
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return gav.version();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return gav.groupId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return gav.artifactId();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 2024 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -12,13 +12,17 @@ package org.springframework.ide.vscode.commons.java;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Gav;
|
||||
|
||||
public interface IProjectBuild {
|
||||
|
||||
String getType();
|
||||
|
||||
URI getBuildFile();
|
||||
|
||||
static IProjectBuild create(String type, URI buildFile) {
|
||||
IGav getGav();
|
||||
|
||||
static IProjectBuild create(String type, URI buildFile, Gav gav) {
|
||||
return new IProjectBuild() {
|
||||
|
||||
@Override
|
||||
@@ -30,6 +34,10 @@ public interface IProjectBuild {
|
||||
public URI getBuildFile() {
|
||||
return buildFile;
|
||||
}
|
||||
|
||||
public IGav getGav() {
|
||||
return IGav.create(gav);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -15,6 +15,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExecuteCommandHandler {
|
||||
CompletableFuture<Object> handle(ExecuteCommandParams params);
|
||||
public interface ExecuteCommandHandler<T> {
|
||||
CompletableFuture<T> handle(ExecuteCommandParams params);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.protocol.java;
|
||||
|
||||
public record Gav(String groupId, String artifactId, String version) {
|
||||
|
||||
}
|
||||
@@ -10,39 +10,24 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.protocol.java;
|
||||
|
||||
public class ProjectBuild {
|
||||
public record ProjectBuild(String type, String buildFile, Gav gav) {
|
||||
|
||||
public static final String MAVEN_PROJECT_TYPE = "maven";
|
||||
public static final String GRADLE_PROJECT_TYPE = "gradle";
|
||||
|
||||
private String type;
|
||||
public static ProjectBuild createMavenBuild(String buildFile, Gav gav) {
|
||||
return new ProjectBuild(MAVEN_PROJECT_TYPE, buildFile, gav);
|
||||
}
|
||||
|
||||
private String buildFile;
|
||||
public static ProjectBuild createGradleBuild(String buildFile, Gav gav) {
|
||||
return new ProjectBuild(GRADLE_PROJECT_TYPE, buildFile, gav);
|
||||
}
|
||||
|
||||
public ProjectBuild(String type, String buildFile) {
|
||||
this.type = type;
|
||||
this.buildFile = buildFile;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getBuildFile() {
|
||||
return buildFile;
|
||||
}
|
||||
|
||||
public void setBuildFile(String buildFile) {
|
||||
this.buildFile = buildFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProjectBuild [type=" + type + ", buildFile=" + buildFile + "]";
|
||||
return "ProjectBuild [type=" + type + ", buildFile=" + buildFile + "gav=" + gav + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.maven.java;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.ClasspathFileBasedCache;
|
||||
@@ -22,9 +23,13 @@ import org.springframework.ide.vscode.commons.java.IProjectBuild;
|
||||
import org.springframework.ide.vscode.commons.java.LegacyJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavadocService;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenCore;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Gav;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild;
|
||||
import org.springframework.ide.vscode.commons.util.FileObserver;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
/**
|
||||
* Wrapper for Maven Core project
|
||||
*
|
||||
@@ -36,10 +41,13 @@ public class MavenJavaProject extends LegacyJavaProject {
|
||||
private static final Logger log = LoggerFactory.getLogger(MavenJavaProject.class);
|
||||
|
||||
private final File pom;
|
||||
|
||||
private Supplier<Gav> gavSupplier;
|
||||
|
||||
private MavenJavaProject(FileObserver fileObserver, Path projectDataCache, IClasspath classpath, File pom, JavadocService javadocService) {
|
||||
super(fileObserver, pom.getParentFile().toURI(), projectDataCache, classpath, javadocService, IProjectBuild.create(ProjectBuild.MAVEN_PROJECT_TYPE, pom.toURI()));
|
||||
private MavenJavaProject(FileObserver fileObserver, Path projectDataCache, IClasspath classpath, File pom, JavadocService javadocService, Supplier<Gav> gavSupplier) {
|
||||
super(fileObserver, pom.getParentFile().toURI(), projectDataCache, classpath, javadocService, IProjectBuild.create(ProjectBuild.MAVEN_PROJECT_TYPE, pom.toURI(), null));
|
||||
this.pom = pom;
|
||||
this.gavSupplier = gavSupplier;
|
||||
}
|
||||
|
||||
public static MavenJavaProject create(FileObserver fileObserver, MavenCore maven, File pom, Path projectDataCache, JavadocService javadocService) {
|
||||
@@ -51,7 +59,17 @@ public class MavenJavaProject extends LegacyJavaProject {
|
||||
() -> new MavenProjectClasspath(maven, pom),
|
||||
fileBasedCache
|
||||
);
|
||||
return new MavenJavaProject(fileObserver, projectDataCache, classpath, pom, javadocService);
|
||||
|
||||
|
||||
return new MavenJavaProject(fileObserver, projectDataCache, classpath, pom, javadocService, Suppliers.memoize(() -> {
|
||||
try {
|
||||
MavenProject p = maven.readProject(pom, false);
|
||||
return new Gav(p.getGroupId(), p.getArtifactId(), p.getVersion());
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static MavenJavaProject create(FileObserver fileObserver, MavenCore maven, File pom, JavadocService javadocService) {
|
||||
@@ -83,4 +101,10 @@ public class MavenJavaProject extends LegacyJavaProject {
|
||||
public String toString() {
|
||||
return "MavenJavaProject("+getElementName()+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IProjectBuild getProjectBuild() {
|
||||
return IProjectBuild.create(ProjectBuild.MAVEN_PROJECT_TYPE, pom.toURI(), gavSupplier.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -156,23 +156,24 @@ public class MavenProjectClasspath implements IClasspath {
|
||||
});
|
||||
entries.add(cpe);
|
||||
}
|
||||
{ //test/java
|
||||
File sourceFolder = new File(project.getBuild().getTestSourceDirectory());
|
||||
if (sourceFolder.exists()) {
|
||||
File outputFolder = new File(project.getBuild().getTestOutputDirectory());
|
||||
CPE cpe = CPE.source(sourceFolder, outputFolder);
|
||||
cpe.setOwn(true);
|
||||
cpe.setTest(true);
|
||||
cpe.setJavaContent(true);
|
||||
safe(() -> {
|
||||
String reportingDir = project.getModel().getReporting().getOutputDirectory();
|
||||
if (reportingDir!=null) {
|
||||
File apidocs = new File(new File(reportingDir), "apidocs");
|
||||
cpe.setJavadocContainerUrl(apidocs.toURI().toURL());
|
||||
}
|
||||
});
|
||||
entries.add(cpe);
|
||||
}
|
||||
{ //test/java - duplicated down below without checking if folder exists
|
||||
|
||||
// File sourceFolder = new File(project.getBuild().getTestSourceDirectory());
|
||||
// if (sourceFolder.exists()) {
|
||||
// File outputFolder = new File(project.getBuild().getTestOutputDirectory());
|
||||
// CPE cpe = CPE.source(sourceFolder, outputFolder);
|
||||
// cpe.setOwn(true);
|
||||
// cpe.setTest(true);
|
||||
// cpe.setJavaContent(true);
|
||||
// safe(() -> {
|
||||
// String reportingDir = project.getModel().getReporting().getOutputDirectory();
|
||||
// if (reportingDir!=null) {
|
||||
// File apidocs = new File(new File(reportingDir), "apidocs");
|
||||
// cpe.setJavadocContainerUrl(apidocs.toURI().toURL());
|
||||
// }
|
||||
// });
|
||||
// entries.add(cpe);
|
||||
// }
|
||||
}
|
||||
{ //main/resources
|
||||
for (Resource resource : project.getBuild().getResources()) {
|
||||
@@ -207,6 +208,8 @@ public class MavenProjectClasspath implements IClasspath {
|
||||
File outputFolder = new File(project.getBuild().getTestOutputDirectory());
|
||||
CPE cpe = CPE.source(sourceFolder, outputFolder);
|
||||
cpe.setOwn(true);
|
||||
cpe.setTest(true);
|
||||
cpe.setJavaContent(true);
|
||||
safe(() -> {
|
||||
String reportingDir = project.getModel().getReporting().getOutputDirectory();
|
||||
if (reportingDir!=null) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src/"/>
|
||||
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
||||
<classpathentry exported="true" kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry exported="true" kind="lib" path="lib/commons-lsp-extensions.jar"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -13,7 +13,9 @@ Require-Bundle: org.eclipse.core.runtime,
|
||||
org.eclipse.lsp4j,
|
||||
org.eclipse.lsp4j.jsonrpc,
|
||||
io.projectreactor.reactor-core,
|
||||
org.reactivestreams.reactive-streams
|
||||
org.reactivestreams.reactive-streams,
|
||||
org.eclipse.m2e.core,
|
||||
org.eclipse.buildship.core
|
||||
Export-Package: org.springframework.ide.vscode.commons.protocol,
|
||||
org.springframework.ide.vscode.commons.protocol.java,
|
||||
org.springframework.ide.vscode.commons.protocol.spring,
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.buildship.core.internal.configuration.GradleProjectNature;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
@@ -36,16 +37,19 @@ import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.JavaModelException;
|
||||
import org.eclipse.jdt.internal.core.JavaProject;
|
||||
import org.eclipse.m2e.core.MavenPlugin;
|
||||
import org.eclipse.m2e.core.embedder.ArtifactKey;
|
||||
import org.eclipse.m2e.core.project.IMavenProjectFacade;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Gav;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild;
|
||||
import org.springframework.tooling.jdt.ls.commons.Logger;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class ClasspathUtil {
|
||||
|
||||
private static final Object JRE_CONTAINER_ID = "org.eclipse.jdt.launching.JRE_CONTAINER";
|
||||
private static final String GRADLE_CONTAINER_ID = "org.eclipse.buildship.core.gradleclasspathcontainer";
|
||||
private static final String MAVEN_CONTAINER_ID = "org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER";
|
||||
|
||||
private static Set<String> getSystemLibraryPaths(IJavaProject javaProject) {
|
||||
try {
|
||||
@@ -222,36 +226,40 @@ public class ClasspathUtil {
|
||||
}
|
||||
|
||||
public static ProjectBuild createProjectBuild(IJavaProject jp) {
|
||||
try {
|
||||
// try {
|
||||
boolean likelyGradle = false;
|
||||
boolean likelyMaven = false;
|
||||
final Path home = System.getProperty("user.home") == null ? null : new File(System.getProperty("user.home")).toPath();
|
||||
for (IClasspathEntry e : jp.getRawClasspath()) {
|
||||
switch (e.getPath().segment(0)) {
|
||||
case MAVEN_CONTAINER_ID:
|
||||
IFile f = jp.getProject().getFile("pom.xml");
|
||||
return new ProjectBuild(ProjectBuild.MAVEN_PROJECT_TYPE, f.exists() ? f.getLocationURI().toASCIIString() : null);
|
||||
case GRADLE_CONTAINER_ID:
|
||||
IFile g = jp.getProject().getFile("build.gradle");
|
||||
if (!g.exists()) {
|
||||
g = jp.getProject().getFile("build.gradle.kts");
|
||||
}
|
||||
return new ProjectBuild(ProjectBuild.GRADLE_PROJECT_TYPE, g.exists() ? g.getLocationURI().toASCIIString() : null);
|
||||
default:
|
||||
if (home != null && e.getPath() != null && e.getPath().toFile() != null) {
|
||||
Path path = e.getPath().toFile().toPath();
|
||||
if (path.startsWith(home.resolve(".gradle"))) {
|
||||
likelyGradle = true;
|
||||
} else if (path.startsWith(home.resolve(".m2"))) {
|
||||
likelyMaven = true;
|
||||
if (MavenPlugin.isMavenProject(jp.getProject())) {
|
||||
IMavenProjectFacade facade = MavenPlugin.getMavenProjectRegistry().getProject(jp.getProject());
|
||||
ArtifactKey artifact = facade.getArtifactKey();
|
||||
return ProjectBuild.createMavenBuild(facade.getPom().getLocationURI().toASCIIString(), new Gav(artifact.groupId(), artifact.artifactId(), artifact.version()));
|
||||
} else if (GradleProjectNature.isPresentOn(jp.getProject())) {
|
||||
IFile g = jp.getProject().getFile("build.gradle");
|
||||
if (!g.exists()) {
|
||||
g = jp.getProject().getFile("build.gradle.kts");
|
||||
}
|
||||
return new ProjectBuild(ProjectBuild.GRADLE_PROJECT_TYPE, g.exists() ? g.getLocationURI().toASCIIString() : null, null);
|
||||
} else {
|
||||
try {
|
||||
for (IClasspathEntry e : jp.getRawClasspath()) {
|
||||
if (home != null && e.getPath() != null && e.getPath().toFile() != null) {
|
||||
Path path = e.getPath().toFile().toPath();
|
||||
if (path.startsWith(home.resolve(".gradle"))) {
|
||||
likelyGradle = true;
|
||||
} else if (path.startsWith(home.resolve(".m2"))) {
|
||||
likelyMaven = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (likelyMaven) {
|
||||
IFile f = jp.getProject().getFile("pom.xml");
|
||||
if (f.exists()) {
|
||||
return new ProjectBuild(ProjectBuild.MAVEN_PROJECT_TYPE, f.getLocationURI().toASCIIString());
|
||||
return ProjectBuild.createMavenBuild(f.getLocationURI().toASCIIString(), null);
|
||||
}
|
||||
} else if (likelyGradle) {
|
||||
IFile g = jp.getProject().getFile("build.gradle");
|
||||
@@ -259,12 +267,12 @@ public class ClasspathUtil {
|
||||
g = jp.getProject().getFile("build.gradle.kts");
|
||||
}
|
||||
if (g.exists()) {
|
||||
return new ProjectBuild(ProjectBuild.GRADLE_PROJECT_TYPE, g.getLocationURI().toASCIIString());
|
||||
return ProjectBuild.createGradleBuild(g.getLocationURI().toASCIIString(), null);
|
||||
}
|
||||
}
|
||||
} catch (JavaModelException e) {
|
||||
// ignore
|
||||
}
|
||||
return new ProjectBuild(null, null);
|
||||
// } catch (JavaModelException e) {
|
||||
// // ignore
|
||||
// }
|
||||
return new ProjectBuild(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class CommandsConfig {
|
||||
|
||||
@Bean WorkspaceBootExecutableProjects workspaceBootProjects(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) {
|
||||
return new WorkspaceBootExecutableProjects(server, projectFinder, symbolIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.commons.java.IGav;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.BeansParams;
|
||||
|
||||
public class WorkspaceBootExecutableProjects {
|
||||
|
||||
public record ExecutableProject(String name, String uri, String gav, String mainClass, Collection<String> classpath) {}
|
||||
|
||||
final static String CMD = "sts/spring-boot/executableBootProjects";
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(WorkspaceBootExecutableProjects.class);
|
||||
|
||||
final private JavaProjectFinder projectFinder;
|
||||
final private SpringSymbolIndex symbolIndex;
|
||||
|
||||
public WorkspaceBootExecutableProjects(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.symbolIndex = symbolIndex;
|
||||
server.onCommand(CMD, params -> findExecutableProjects());
|
||||
}
|
||||
|
||||
private CompletableFuture<Optional<ExecutableProject>> mapToExecProject(IJavaProject project) {
|
||||
BeansParams params = new BeansParams();
|
||||
params.setProjectName(project.getElementName());
|
||||
return symbolIndex.beans(params).thenApply(beans -> {
|
||||
List<Bean> bootAppBeans = beans.stream()
|
||||
.filter(b -> Arrays.asList(b.getAnnotations()).contains(Annotations.BOOT_APP))
|
||||
.limit(2)
|
||||
.collect(Collectors.toList());
|
||||
if (bootAppBeans.size() == 1) {
|
||||
try {
|
||||
Bean appBean = bootAppBeans.get(0);
|
||||
Collection<String> classpath = project.getClasspath().getClasspathEntries().stream()
|
||||
.filter(cpe -> !cpe.isTest() && !cpe.isSystem())
|
||||
.map(cpe -> Classpath.isSource(cpe) ? cpe.getOutputFolder() : cpe.getPath())
|
||||
.collect(Collectors.toSet());
|
||||
IGav gav = project.getProjectBuild().getGav();
|
||||
String gavStr = "%s:%s:%s".formatted(gav.getGroupId(), gav.getArtifactId(), gav.getVersion());
|
||||
return Optional.of(new ExecutableProject(project.getElementName(), project.getLocationUri().toASCIIString(), gavStr, appBean.getType(), classpath));
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<List<ExecutableProject>> findExecutableProjects() {
|
||||
List<CompletableFuture<Optional<ExecutableProject>>> futures = projectFinder.all().stream()
|
||||
.filter(p -> p.getProjectBuild().getGav() != null)
|
||||
.filter(p -> SpringProjectUtil.isBootProject(p))
|
||||
.map(this::mapToExecProject)
|
||||
.collect(Collectors.toList());
|
||||
List<ExecutableProject> executableProjects = Collections.synchronizedList(new ArrayList<>());
|
||||
futures.forEach(f -> f.thenAccept(opt -> opt.ifPresent(executableProjects::add)));
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).thenApply(v -> executableProjects);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -385,7 +385,7 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService, Serv
|
||||
}
|
||||
|
||||
private static IProjectBuild from(ProjectBuild projectBuild) {
|
||||
return projectBuild == null ? null : IProjectBuild.create(projectBuild.getType(), projectBuild.getBuildFile() == null ? null : URI.create(projectBuild.getBuildFile()));
|
||||
return projectBuild == null ? null : IProjectBuild.create(projectBuild.type(), projectBuild.buildFile() == null ? null : URI.create(projectBuild.buildFile()), projectBuild.gav());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects.ExecutableProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class WorkspaceBootExecutableProjectsTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringSymbolIndex symbolIndex;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void singleProject() throws Exception {
|
||||
MavenJavaProject project = ProjectsHarness.INSTANCE.mavenProject("test-spring-indexing");
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(project.getProjectBuild().getBuildFile().toASCIIString())).get();
|
||||
|
||||
CompletableFuture<Void> initProject = symbolIndex.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
List<WorkspaceBootExecutableProjects.ExecutableProject> res = (List<WorkspaceBootExecutableProjects.ExecutableProject>) harness.getServer().getWorkspaceService().executeCommand(new ExecuteCommandParams(WorkspaceBootExecutableProjects.CMD, Collections.emptyList())).get();
|
||||
assertNotNull(res);
|
||||
assertEquals(1, res.size());
|
||||
ExecutableProject execProject = res.get(0);
|
||||
assertEquals("test-spring-indexing", execProject.name());
|
||||
assertEquals("org.test.MainClass", execProject.mainClass());
|
||||
assertEquals(project.getLocationUri().toASCIIString(), execProject.uri());
|
||||
assertEquals("com.example:test-spring-indexing:0.0.1-SNAPSHOT", execProject.gav());
|
||||
assertEquals(99, execProject.classpath().size());
|
||||
|
||||
assertTrue(execProject.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
|
||||
assertFalse(execProject.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void multipleProjects() throws Exception {
|
||||
MavenJavaProject project1 = ProjectsHarness.INSTANCE.mavenProject("test-spring-indexing");
|
||||
MavenJavaProject project2 = ProjectsHarness.INSTANCE.mavenProject("test-spring-data-symbols");
|
||||
ProjectsHarness.INSTANCE.mavenProject("test-annotation-indexing-non-boot-project");
|
||||
ProjectsHarness.INSTANCE.mavenProject("test-xml-validations");
|
||||
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(project1.getProjectBuild().getBuildFile().toASCIIString())).get();
|
||||
projectFinder.find(new TextDocumentIdentifier(project2.getProjectBuild().getBuildFile().toASCIIString())).get();
|
||||
|
||||
CompletableFuture<Void> initProject = symbolIndex.waitOperation();
|
||||
initProject.get(10, TimeUnit.SECONDS);
|
||||
|
||||
List<WorkspaceBootExecutableProjects.ExecutableProject> res = (List<WorkspaceBootExecutableProjects.ExecutableProject>) harness.getServer().getWorkspaceService().executeCommand(new ExecuteCommandParams(WorkspaceBootExecutableProjects.CMD, Collections.emptyList())).get();
|
||||
assertNotNull(res);
|
||||
assertEquals(2, res.size());
|
||||
|
||||
ExecutableProject execProject2 = res.get(0);
|
||||
assertEquals("test-spring-data-symbols", execProject2.name());
|
||||
assertEquals("org.test.Application", execProject2.mainClass());
|
||||
assertEquals(project2.getLocationUri().toASCIIString(), execProject2.uri());
|
||||
assertEquals("com.example:test-spring-data-symbols:0.0.1-SNAPSHOT", execProject2.gav());
|
||||
assertEquals(44, execProject2.classpath().size());
|
||||
assertTrue(execProject2.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
|
||||
assertFalse(execProject2.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
|
||||
|
||||
ExecutableProject execProject1 = res.get(1);
|
||||
assertEquals("test-spring-indexing", execProject1.name());
|
||||
assertEquals("org.test.MainClass", execProject1.mainClass());
|
||||
assertEquals(project1.getLocationUri().toASCIIString(), execProject1.uri());
|
||||
assertEquals("com.example:test-spring-indexing:0.0.1-SNAPSHOT", execProject1.gav());
|
||||
assertEquals(99, execProject1.classpath().size());
|
||||
assertTrue(execProject1.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
|
||||
assertFalse(execProject1.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
import * as OS from "os";
|
||||
import * as VSCode from 'vscode';
|
||||
import { workspace } from 'vscode';
|
||||
import {
|
||||
commands,
|
||||
window,
|
||||
workspace,
|
||||
ExtensionContext,
|
||||
Uri
|
||||
} from 'vscode';
|
||||
|
||||
import * as commons from '@pivotal-tools/commons-vscode';
|
||||
import * as liveHoverUi from './live-hover-connect-ui';
|
||||
@@ -24,7 +29,7 @@ const FACTORIES_LANGUAGE_ID = "spring-factories";
|
||||
const STOP_ASKING = "Stop Asking";
|
||||
|
||||
/** Called when extension is activated */
|
||||
export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAPI> {
|
||||
export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
|
||||
// registerPipelineGenerator(context);
|
||||
let options : commons.ActivatorOptions = {
|
||||
@@ -36,14 +41,14 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
|
||||
vmArgs: [
|
||||
'-XX:+HeapDumpOnOutOfMemoryError'
|
||||
],
|
||||
checkjvm: (context: VSCode.ExtensionContext, jvm: commons.JVM) => {
|
||||
checkjvm: (context: ExtensionContext, jvm: commons.JVM) => {
|
||||
let version = jvm.getMajorVersion();
|
||||
if (version < 17) {
|
||||
throw Error(`Spring Tools Language Server requires Java 17 or higher to be launched. Current Java version is ${version}`);
|
||||
}
|
||||
|
||||
if (!jvm.isJdk()) {
|
||||
VSCode.window.showWarningMessage(
|
||||
window.showWarningMessage(
|
||||
'JAVA_HOME or PATH environment variable seems to point to a JRE. A JDK is required, hence Boot Hints are unavailable.',
|
||||
STOP_ASKING).then(selection => {
|
||||
if (selection === STOP_ASKING) {
|
||||
@@ -58,7 +63,7 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
|
||||
mainClass: 'org.springframework.ide.vscode.boot.app.BootLanguageServerBootApp',
|
||||
configFileName: 'application.properties'
|
||||
},
|
||||
workspaceOptions: VSCode.workspace.getConfiguration("spring-boot.ls"),
|
||||
workspaceOptions: workspace.getConfiguration("spring-boot.ls"),
|
||||
clientOptions: {
|
||||
markdown: {
|
||||
isTrusted: true
|
||||
@@ -88,7 +93,7 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
|
||||
}
|
||||
return uri.toString();
|
||||
},
|
||||
protocol2Code: uri => VSCode.Uri.parse(uri)
|
||||
protocol2Code: uri => Uri.parse(uri)
|
||||
},
|
||||
// See PT-158992999 as to why a scheme is added to the document selector
|
||||
// documentSelector: [ PROPERTIES_LANGUAGE_ID, YAML_LANGUAGE_ID, JAVA_LANGUAGE_ID ],
|
||||
@@ -132,34 +137,34 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
|
||||
highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on'
|
||||
};
|
||||
|
||||
// Register launch config contributior to java debug launch to be able to connect to JMX
|
||||
context.subscriptions.push(startDebugSupport());
|
||||
|
||||
return commons.activate(options, context).then(client => {
|
||||
VSCode.commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => {
|
||||
commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => {
|
||||
// Boot LS is fully started
|
||||
registerClasspathService(client);
|
||||
registerJavaDataService(client);
|
||||
|
||||
// Force classpath listener to be enabled. Boot LS can only be launched iff classpath is available and there Spring-Boot on the classpath somewhere.
|
||||
VSCode.commands.executeCommand('sts.vscode-spring-boot.enableClasspathListening', true);
|
||||
commands.executeCommand('sts.vscode-spring-boot.enableClasspathListening', true);
|
||||
|
||||
// Register launch config contributior to java debug launch to be able to connect to JMX
|
||||
context.subscriptions.push(startDebugSupport());
|
||||
}));
|
||||
VSCode.commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop());
|
||||
commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop());
|
||||
liveHoverUi.activate(client, options, context);
|
||||
rewrite.activate(client, options, context);
|
||||
setLogLevelUi.activate(client, options, context);
|
||||
|
||||
VSCode.commands.registerCommand('vscode-spring-boot.spring.modulith.metadata.refresh', async () => {
|
||||
const modulithProjects = await VSCode.commands.executeCommand('sts/modulith/projects');
|
||||
commands.registerCommand('vscode-spring-boot.spring.modulith.metadata.refresh', async () => {
|
||||
const modulithProjects = await commands.executeCommand('sts/modulith/projects');
|
||||
const projectNames = Object.keys(modulithProjects);
|
||||
if (projectNames.length === 0) {
|
||||
VSCode.window.showErrorMessage('No Spring Modulith projects found');
|
||||
window.showErrorMessage('No Spring Modulith projects found');
|
||||
} else {
|
||||
const projectName = projectNames.length === 1 ? projectNames[0] : await VSCode.window.showQuickPick(
|
||||
const projectName = projectNames.length === 1 ? projectNames[0] : await window.showQuickPick(
|
||||
projectNames,
|
||||
{ placeHolder: "Select the target project." },
|
||||
);
|
||||
VSCode.commands.executeCommand('sts/modulith/metadata/refresh', modulithProjects[projectName]);
|
||||
commands.executeCommand('sts/modulith/metadata/refresh', modulithProjects[projectName]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, WorkspaceFolder } from "vscode";
|
||||
import { debug,
|
||||
window,
|
||||
commands,
|
||||
workspace,
|
||||
CancellationToken,
|
||||
DebugConfiguration,
|
||||
DebugConfigurationProvider,
|
||||
WorkspaceFolder,
|
||||
DebugConfigurationProviderTriggerKind,
|
||||
DebugSession,
|
||||
DebugSessionCustomEvent,
|
||||
Disposable
|
||||
} from "vscode";
|
||||
import * as path from "path";
|
||||
import * as VSCode from "vscode";
|
||||
import { Disposable } from "vscode";
|
||||
import psList from 'ps-list';
|
||||
import { ListenablePreferenceSetting } from "@pivotal-tools/commons-vscode/lib/launch-util";
|
||||
import * as fs from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
const JMX_VM_ARG = '-Dspring.jmx.enabled='
|
||||
const ACTUATOR_JMX_EXPOSURE_ARG = '-Dmanagement.endpoints.jmx.exposure.include='
|
||||
@@ -11,15 +23,57 @@ const ADMIN_VM_ARG = '-Dspring.application.admin.enabled='
|
||||
const BOOT_PROJECT_ARG = '-Dspring.boot.project.name=';
|
||||
const RMI_HOSTNAME = '-Djava.rmi.server.hostname=localhost';
|
||||
|
||||
const ENV_TESTJAR_ARTIFACT_PREFIX = "TESTJARS_ARTIFACT_";
|
||||
|
||||
const TEST_RUNNER_MAIN_CLASSES = [
|
||||
'org.eclipse.jdt.internal.junit.runner.RemoteTestRunner',
|
||||
'com.microsoft.java.test.runner.Launcher'
|
||||
];
|
||||
|
||||
interface ExecutableBootProject {
|
||||
name: string;
|
||||
uri: string;
|
||||
mainClass: string;
|
||||
classpath: string[];
|
||||
gav: string;
|
||||
}
|
||||
|
||||
interface ProcessEvent {
|
||||
type: string;
|
||||
processId: number;
|
||||
shellProcessId: number
|
||||
}
|
||||
|
||||
class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
|
||||
resolveDebugConfigurationWithSubstitutedVariables(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration> {
|
||||
if (!TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
async resolveDebugConfigurationWithSubstitutedVariables(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): Promise<DebugConfiguration> {
|
||||
// TestJar launch support
|
||||
if (TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isTestJarsOnClasspath(debugConfiguration)) {
|
||||
const projects = await commands.executeCommand("sts/spring-boot/executableBootProjects") as ExecutableBootProject[];
|
||||
let env = debugConfiguration.env;
|
||||
if (!env) {
|
||||
env = {};
|
||||
debugConfiguration.env = env;
|
||||
}
|
||||
const projectsWithErrors: ExecutableBootProject[] = [];
|
||||
// Create all project classparth data files and add env vars for workspace projects
|
||||
await Promise.all(projects.map(async p => {
|
||||
const envName = this.createEnvVarName(p);
|
||||
if (!env[envName]) {
|
||||
try {
|
||||
env[envName] = await this.createFile(p);
|
||||
} catch (error) {
|
||||
projectsWithErrors.push(p);
|
||||
}
|
||||
}
|
||||
}));
|
||||
if (projectsWithErrors.length > 0) {
|
||||
const projectStr = projectsWithErrors.map(p => `'${p.name}'`);
|
||||
window.showWarningMessage(`TestJar Support: Could not provide data for workspace projects: ${projectStr}`);
|
||||
}
|
||||
}
|
||||
// Running app live hovers support
|
||||
if (isAutoConnectOn() && !TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
if (debugConfiguration.vmArgs) {
|
||||
if (debugConfiguration.vmArgs.indexOf(JMX_VM_ARG) < 0) {
|
||||
debugConfiguration.vmArgs += ` ${JMX_VM_ARG}true`;
|
||||
@@ -43,58 +97,46 @@ class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
return debugConfiguration;
|
||||
}
|
||||
|
||||
}
|
||||
private createEnvVarName(project: ExecutableBootProject) {
|
||||
return `${ENV_TESTJAR_ARTIFACT_PREFIX}${project.gav.replace(/:/g, "_")}`;
|
||||
}
|
||||
|
||||
interface ProcessEvent {
|
||||
type: string;
|
||||
processId: number;
|
||||
shellProcessId: number
|
||||
}
|
||||
private async createFile(project: ExecutableBootProject) {
|
||||
const filePath = path.join(tmpdir(), `${project.gav.replace(/:/g, "_")}-${randomUUID()}`);
|
||||
await fs.writeFile(filePath, `# the main class to invoke\nmain=${project.mainClass}\n# the classpath to use delimited by the OS specific delimiters\nclasspath=${project.classpath.join(path.delimiter)}`, function(err) {
|
||||
if(err) {
|
||||
throw Error();
|
||||
}
|
||||
});
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function hookListenerToBooleanPreference(setting: string, listenerCreator: () => Disposable): Disposable {
|
||||
const listenableSetting = new ListenablePreferenceSetting<boolean>(setting);
|
||||
let listener: Disposable | undefined = listenableSetting.value ? listenerCreator() : undefined;
|
||||
listenableSetting.onDidChangeValue(() => {
|
||||
if (listenableSetting.value) {
|
||||
if (!listener) {
|
||||
listener = listenerCreator();
|
||||
}
|
||||
} else {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
listener = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
}
|
||||
listenableSetting.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function startDebugSupport(): Disposable {
|
||||
return hookListenerToBooleanPreference(
|
||||
'boot-java.live-information.automatic-connection.on',
|
||||
() => Disposable.from(
|
||||
VSCode.debug.onDidReceiveDebugSessionCustomEvent(handleCustomDebugEvent),
|
||||
VSCode.debug.registerDebugConfigurationProvider('java', new SpringBootDebugConfigProvider(), VSCode.DebugConfigurationProviderTriggerKind.Initial)
|
||||
)
|
||||
return Disposable.from(
|
||||
debug.onDidReceiveDebugSessionCustomEvent(handleCustomDebugEvent),
|
||||
debug.onDidTerminateDebugSession(cleanupDebugSession),
|
||||
debug.registerDebugConfigurationProvider('java', new SpringBootDebugConfigProvider(), DebugConfigurationProviderTriggerKind.Initial),
|
||||
new Disposable(() => cleanupDebugSession(debug.activeDebugSession)) // If VSCode is shutdown then clean active debug session if it satidfies conditions
|
||||
);
|
||||
}
|
||||
|
||||
async function handleCustomDebugEvent(e: VSCode.DebugSessionCustomEvent): Promise<void> {
|
||||
if (e.session?.type === 'java' && e?.body?.type === 'processid') {
|
||||
async function cleanupDebugSession(session: DebugSession) {
|
||||
// Handle termination of a Boot app with TestJars on the classpath
|
||||
if (session.type === 'java' && TEST_RUNNER_MAIN_CLASSES.includes(session.configuration.mainClass) && isTestJarsOnClasspath(session.configuration) && session.configuration.env) {
|
||||
await Promise.all(Object.keys(session.configuration.env).filter(k => k.startsWith(ENV_TESTJAR_ARTIFACT_PREFIX)).map(k => fs.rm(session.configuration.env[k], () => {})));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCustomDebugEvent(e: DebugSessionCustomEvent): Promise<void> {
|
||||
if (isAutoConnectOn() && e.session?.type === 'java' && e?.body?.type === 'processid') {
|
||||
const debugConfiguration: DebugConfiguration = e.session.configuration;
|
||||
if (canConnect(debugConfiguration)) {
|
||||
setTimeout(async () => {
|
||||
const pid = await getAppPid(e.body as ProcessEvent);
|
||||
const processKey = pid.toString();
|
||||
VSCode.commands.executeCommand('sts/livedata/connect', { processKey });
|
||||
commands.executeCommand('sts/livedata/connect', { processKey });
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
@@ -130,6 +172,26 @@ function isActuatorJarFile(f: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isTestJarsOnClasspath(debugConfiguration: DebugConfiguration): boolean {
|
||||
if (Array.isArray(debugConfiguration.classPaths)) {
|
||||
return !!debugConfiguration.classPaths.find(isTestJarFile);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
function isTestJarFile(f: string): boolean {
|
||||
const fileName = path.basename(f || "");
|
||||
if (/^spring-boot-testjars-\d+\.\d+\.\d+(.*)?.jar$/.test(fileName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAutoConnectOn(): boolean {
|
||||
return workspace.getConfiguration().get("boot-java.live-information.automatic-connection.on", true);
|
||||
}
|
||||
|
||||
function canConnect(debugConfiguration: DebugConfiguration): boolean {
|
||||
if (!TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
return debugConfiguration.vmArgs
|
||||
|
||||
Reference in New Issue
Block a user