Preference setting to turn on/off TestJars support
This commit is contained in:
@@ -48,7 +48,7 @@ public class BootLaunchActivator extends AbstractUIPlugin {
|
||||
myStore.setValue("cglib.breakpoint.warning.disabled", true);
|
||||
}
|
||||
|
||||
launchManager.addLaunchListener(TestJarLaunchListener.getSingletonInstance());
|
||||
TestJarSupport.start();
|
||||
}
|
||||
|
||||
private void setPreference(String plugin, String key, boolean value) {
|
||||
@@ -74,8 +74,7 @@ public class BootLaunchActivator extends AbstractUIPlugin {
|
||||
if (workspaceListener!=null) {
|
||||
workspaceListener.dispose();
|
||||
}
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
launchManager.removeLaunchListener(TestJarLaunchListener.getSingletonInstance());
|
||||
TestJarSupport.stop();
|
||||
super.stop(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,7 @@ 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;
|
||||
class TestJarLaunchListener implements ILaunchesListener2 {
|
||||
|
||||
private static final Pattern TESTJAR_PATTERN = Pattern.compile("^spring-boot-testjars-\\d+\\.\\d+\\.\\d+(.*)?.jar$");
|
||||
|
||||
@@ -54,13 +52,6 @@ public class TestJarLaunchListener implements ILaunchesListener2 {
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -207,4 +198,12 @@ public class TestJarLaunchListener implements ILaunchesListener2 {
|
||||
}
|
||||
}
|
||||
|
||||
void clearTestJarArtifactEnvKeyFromLaunches(ILaunchManager launchManager) {
|
||||
for (ILaunch l : launchManager.getLaunches()) {
|
||||
if (!l.isTerminated() && l.getLaunchConfiguration() != null) {
|
||||
clearTestJarWorkspaceProjectFiles(l.getLaunchConfiguration());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* 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 org.eclipse.debug.core.DebugPlugin;
|
||||
import org.eclipse.debug.core.ILaunchManager;
|
||||
import org.eclipse.jface.util.IPropertyChangeListener;
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springframework.ide.eclipse.boot.core.BootPreferences;
|
||||
|
||||
public class TestJarSupport {
|
||||
|
||||
private static final TestJarLaunchListener LAUNCHES_LISTENER = new TestJarLaunchListener();
|
||||
|
||||
private static final IPropertyChangeListener PREFERENCE_LISTENER = propertyChange -> {
|
||||
if (BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT.equals(propertyChange.getProperty())) {
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
if (BootActivator.getDefault().getPreferenceStore().getBoolean(BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT)) {
|
||||
// TestJars support ON
|
||||
launchManager.addLaunchListener(LAUNCHES_LISTENER);
|
||||
} else {
|
||||
// TestJars support OFF
|
||||
launchManager.removeLaunchListener(LAUNCHES_LISTENER);
|
||||
LAUNCHES_LISTENER.clearTestJarArtifactEnvKeyFromLaunches(launchManager);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void start() {
|
||||
if (BootActivator.getDefault().getPreferenceStore().getBoolean(BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT)) {
|
||||
BootActivator.getDefault().getPreferenceStore().addPropertyChangeListener(PREFERENCE_LISTENER);
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
launchManager.addLaunchListener(LAUNCHES_LISTENER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (BootActivator.getDefault().getPreferenceStore().getBoolean(BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT)) {
|
||||
BootActivator.getDefault().getPreferenceStore().removePropertyChangeListener(PREFERENCE_LISTENER);
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
launchManager.removeLaunchListener(LAUNCHES_LISTENER);
|
||||
LAUNCHES_LISTENER.clearTestJarArtifactEnvKeyFromLaunches(launchManager);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2017 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
|
||||
@@ -44,6 +44,9 @@ public class BootPreferences implements IPreferenceChangeListener {
|
||||
public static final String PREF_BOOT_FAST_STARTUP_JVM_ARGS = "org.springframework.ide.eclipse.boot.FastStartupJvmArgs";
|
||||
public static final String BOOT_FAST_STARTUP_DEFAULT_JVM_ARGS = "-XX:TieredStopAtLevel=1";
|
||||
|
||||
public static final String PREF_BOOT_TESTJARS_LAUNCH_SUPPORT = "org.springframework.ide.eclipse.boot.TestJarsLaunch";
|
||||
|
||||
|
||||
// Boot Preference Page ID
|
||||
public static final String BOOT_PREFERENCE_PAGE_ID = "org.springframework.ide.eclipse.boot.ui.preferences.BootPreferencePage";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2023 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 static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOO
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_FAST_STARTUP_REMIND_MESSAGE;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_THIN_WRAPPER;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_IGNORE_SILENT_EXIT;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT;
|
||||
|
||||
import org.eclipse.debug.internal.ui.preferences.BooleanFieldEditor2;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
@@ -71,6 +72,8 @@ public class BootPreferencePage extends FieldEditorPreferencePage implements IWo
|
||||
thinLauncher.setErrorMessage("Thin launcher must be an existing file");
|
||||
setTooltip(thinLauncherComposite, thinLauncher, "Thin boot launcher jar to use in Spring Boot Launch configuration (when that option is enabled in the launch config)");
|
||||
addField(thinLauncher);
|
||||
|
||||
addField(new BooleanFieldEditor2(PREF_BOOT_TESTJARS_LAUNCH_SUPPORT, "TestJars Support", SWT.CHECK, launchGroup));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 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
|
||||
@@ -17,6 +17,7 @@ import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOO
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_FAST_STARTUP_REMIND_MESSAGE;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_FAST_STARTUP_JVM_ARGS;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.BOOT_FAST_STARTUP_DEFAULT_JVM_ARGS;
|
||||
import static org.springframework.ide.eclipse.boot.core.BootPreferences.PREF_BOOT_TESTJARS_LAUNCH_SUPPORT;
|
||||
|
||||
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
@@ -25,7 +26,7 @@ import org.springsource.ide.eclipse.commons.core.preferences.StsProperties;
|
||||
|
||||
/**
|
||||
* Initializer of default values for the Spring Boot support
|
||||
*
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
@@ -34,7 +35,7 @@ public class PreferencesInitializer extends AbstractPreferenceInitializer {
|
||||
@Override
|
||||
public void initializeDefaultPreferences() {
|
||||
IPreferenceStore store = BootActivator.getDefault().getPreferenceStore();
|
||||
|
||||
|
||||
/*
|
||||
* Note that line below cannot be moved to BootPreferencePage static
|
||||
* init method because the class has BooleanFieldEditor2 import that in
|
||||
@@ -42,13 +43,15 @@ public class PreferencesInitializer extends AbstractPreferenceInitializer {
|
||||
* because workbench may not be started in the case of a unit test
|
||||
*/
|
||||
store.setDefault(PREF_IGNORE_SILENT_EXIT, DEFAULT_PREF_IGNORE_SILENT_EXIT);
|
||||
|
||||
|
||||
store.setDefault(PREF_INITIALIZR_URL, StsProperties.getInstance().get("spring.initializr.json.url"));
|
||||
|
||||
|
||||
store.setDefault(PREF_BOOT_FAST_STARTUP_DEFAULT, true);
|
||||
store.setDefault(PREF_BOOT_FAST_STARTUP_REMIND_MESSAGE, true);
|
||||
store.setDefault(PREF_BOOT_FAST_STARTUP_JVM_ARGS, BOOT_FAST_STARTUP_DEFAULT_JVM_ARGS);
|
||||
store.setDefault(BOOT_FAST_STARTUP_DEFAULT_JVM_ARGS, DEFAULT_PREF_IGNORE_SILENT_EXIT);
|
||||
store.setDefault(PREF_BOOT_TESTJARS_LAUNCH_SUPPORT, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ExtensionAPI } from "./api";
|
||||
import {registerClasspathService} from "@pivotal-tools/commons-vscode/lib/classpath";
|
||||
import {registerJavaDataService} from "@pivotal-tools/commons-vscode/lib/java-data";
|
||||
import * as setLogLevelUi from './set-log-levels-ui';
|
||||
import { startTestJarSupport } from "./test-jar-launch";
|
||||
|
||||
const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
|
||||
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
|
||||
@@ -137,6 +138,9 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
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 => {
|
||||
commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => {
|
||||
// Boot LS is fully started
|
||||
@@ -146,8 +150,9 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
// Force classpath listener to be enabled. Boot LS can only be launched iff classpath is available and there Spring-Boot on the classpath somewhere.
|
||||
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());
|
||||
// Register TestJars launch support
|
||||
context.subscriptions.push(startTestJarSupport());
|
||||
|
||||
}));
|
||||
commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop());
|
||||
liveHoverUi.activate(client, options, context);
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { debug,
|
||||
window,
|
||||
commands,
|
||||
workspace,
|
||||
CancellationToken,
|
||||
DebugConfiguration,
|
||||
DebugConfigurationProvider,
|
||||
WorkspaceFolder,
|
||||
DebugConfigurationProviderTriggerKind,
|
||||
DebugSession,
|
||||
DebugSessionCustomEvent,
|
||||
Disposable
|
||||
Disposable,
|
||||
ProviderResult
|
||||
} from "vscode";
|
||||
import * as path from "path";
|
||||
import psList from 'ps-list';
|
||||
import * as fs from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { randomUUID } from "crypto";
|
||||
import { ListenablePreferenceSetting } from "@pivotal-tools/commons-vscode/lib/launch-util";
|
||||
|
||||
const JMX_VM_ARG = '-Dspring.jmx.enabled='
|
||||
const ACTUATOR_JMX_EXPOSURE_ARG = '-Dmanagement.endpoints.jmx.exposure.include='
|
||||
@@ -23,21 +19,11 @@ 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 = [
|
||||
export 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;
|
||||
@@ -46,34 +32,9 @@ interface ProcessEvent {
|
||||
|
||||
class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
resolveDebugConfigurationWithSubstitutedVariables(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration> {
|
||||
// Running app live hovers support
|
||||
if (isAutoConnectOn() && !TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
if (!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`;
|
||||
@@ -97,40 +58,46 @@ class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
return debugConfiguration;
|
||||
}
|
||||
|
||||
private createEnvVarName(project: ExecutableBootProject) {
|
||||
return `${ENV_TESTJAR_ARTIFACT_PREFIX}${project.gav.replace(/:/g, "_")}`;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
export 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();
|
||||
}
|
||||
});
|
||||
return filePath;
|
||||
}
|
||||
} else {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
listener = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
}
|
||||
listenableSetting.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function startDebugSupport(): Disposable {
|
||||
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
|
||||
return hookListenerToBooleanPreference(
|
||||
'boot-java.live-information.automatic-connection.on',
|
||||
() => Disposable.from(
|
||||
debug.onDidReceiveDebugSessionCustomEvent(handleCustomDebugEvent),
|
||||
debug.registerDebugConfigurationProvider('java', new SpringBootDebugConfigProvider(), DebugConfigurationProviderTriggerKind.Initial)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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') {
|
||||
if (e.session?.type === 'java' && e?.body?.type === 'processid') {
|
||||
const debugConfiguration: DebugConfiguration = e.session.configuration;
|
||||
if (canConnect(debugConfiguration)) {
|
||||
setTimeout(async () => {
|
||||
@@ -172,26 +139,6 @@ 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
|
||||
|
||||
100
vscode-extensions/vscode-spring-boot/lib/test-jar-launch.ts
Normal file
100
vscode-extensions/vscode-spring-boot/lib/test-jar-launch.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, DebugConfigurationProviderTriggerKind, DebugSession, Disposable, WorkspaceFolder, commands, debug, window } from "vscode";
|
||||
import { TEST_RUNNER_MAIN_CLASSES, hookListenerToBooleanPreference } from "./debug-config-provider";
|
||||
import path from "path";
|
||||
import { tmpdir } from "os";
|
||||
import { randomUUID } from "crypto";
|
||||
import * as fs from "fs";
|
||||
|
||||
const ENV_TESTJAR_ARTIFACT_PREFIX = "TESTJARS_ARTIFACT_";
|
||||
|
||||
interface ExecutableBootProject {
|
||||
name: string;
|
||||
uri: string;
|
||||
mainClass: string;
|
||||
classpath: string[];
|
||||
gav: string;
|
||||
}
|
||||
|
||||
class TestJarDebugConfigProvider implements DebugConfigurationProvider {
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
return debugConfiguration;
|
||||
}
|
||||
|
||||
private createEnvVarName(project: ExecutableBootProject) {
|
||||
return `${ENV_TESTJAR_ARTIFACT_PREFIX}${project.gav.replace(/:/g, "_")}`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function startTestJarSupport(): Disposable {
|
||||
return hookListenerToBooleanPreference(
|
||||
'boot-java-vscode-only.test-jars',
|
||||
() => Disposable.from(
|
||||
debug.onDidTerminateDebugSession(cleanupDebugSession),
|
||||
debug.registerDebugConfigurationProvider('java', new TestJarDebugConfigProvider(), DebugConfigurationProviderTriggerKind.Initial),
|
||||
new Disposable(() => cleanupDebugSession(debug.activeDebugSession)) // If VSCode is shutdown then clean active debug session if it satisfies conditions
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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], () => {})));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -228,6 +228,11 @@
|
||||
]
|
||||
},
|
||||
"description": "Array of jmx urls pointing to remote spring boot applications to poll for live hover information. A typical url looks something like this: `service:jmx:rmi://localhost:9111/jndi/rmi://localhost:9111/jmxrmi`"
|
||||
},
|
||||
"boot-java-vscode-only.test-jars": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable/Disable Spring Boot TestJars launch environment variables"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user