Skip to content

Add a new ExecFactory extension point to support alternative processes #1763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion debug/org.eclipse.debug.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.debug.core; singleton:=true
Bundle-Version: 3.22.100.qualifier
Bundle-Version: 3.23.0.qualifier
Bundle-Activator: org.eclipse.debug.core.DebugPlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.FactoryConfigurationError;
Expand Down Expand Up @@ -170,6 +173,14 @@ public class DebugPlugin extends Plugin {
*/
public static final String EXTENSION_POINT_PROCESS_FACTORIES = "processFactories"; //$NON-NLS-1$

/**
* Simple identifier constant (value <code>"execFactories"</code>) for the
* exec factories extension point.
*
* @since 3.23
*/
public static final String EXTENSION_POINT_EXEC_FACTORIES = "execFactories"; //$NON-NLS-1$

/**
* Simple identifier constant (value <code>"logicalStructureTypes"</code>) for the
* logical structure types extension point.
Expand Down Expand Up @@ -438,6 +449,11 @@ public class DebugPlugin extends Plugin {
*/
private HashMap<String, IConfigurationElement> fProcessFactories = null;

/**
* List of exec factories.
*/
private List<ExecFactoryFacade> execFactories;

/**
* Service tracker for the workspace service
*/
Expand Down Expand Up @@ -981,7 +997,24 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
* @since 3.14
*/
public static Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput) throws CoreException {
Process p = null;
List<ExecFactoryFacade> factories = DebugPlugin.getDefault().getExecFactories();
Optional<File> directory = shortenWindowsPath(workingDirectory);
Optional<Map<String, String>> envMap = Optional.ofNullable(envp).map(array -> {
Map<String, String> map = new LinkedHashMap<>();
for (String e : array) {
int index = e.indexOf('=');
if (index != -1) {
map.put(e.substring(0, index), e.substring(index + 1));
}
}
return Map.copyOf(map);
});
for (ExecFactoryFacade holder : factories) {
Optional<Process> exec = holder.exec(cmdLine.clone(), directory, envMap, mergeOutput);
if (exec.isPresent()) {
return exec.get();
}
}
try {
// starting with and without merged output could be done with the
// same process builder approach but since the handling of
Expand All @@ -990,25 +1023,18 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
// builder to not break existing caller of this method
if (mergeOutput) {
ProcessBuilder pb = new ProcessBuilder(cmdLine);
if (workingDirectory != null) {
pb.directory(shortenWindowsPath(workingDirectory));
}
directory.ifPresent(pb::directory);
pb.redirectErrorStream(mergeOutput);
if (envp != null) {
if (envMap.isPresent()) {
Map<String, String> env = pb.environment();
env.clear();
for (String e : envp) {
int index = e.indexOf('=');
if (index != -1) {
env.put(e.substring(0, index), e.substring(index + 1));
}
}
env.putAll(envMap.get());
}
p = pb.start();
} else if (workingDirectory == null) {
p = Runtime.getRuntime().exec(cmdLine, envp);
return pb.start();
} else if (directory.isEmpty()) {
return Runtime.getRuntime().exec(cmdLine, envp);
} else {
p = Runtime.getRuntime().exec(cmdLine, envp, shortenWindowsPath(workingDirectory));
return Runtime.getRuntime().exec(cmdLine, envp, directory.get());
}
} catch (IOException e) {
Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e);
Expand All @@ -1021,18 +1047,18 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
if (handler != null) {
Object result = handler.handleStatus(status, null);
if (result instanceof Boolean resultValue && resultValue) {
p = exec(cmdLine, null);
return exec(cmdLine, null);
}
}
}
return p;
return null;
}

// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
private static final int WINDOWS_MAX_PATH = 258;

private static File shortenWindowsPath(File path) {
if (path.getPath().length() > WINDOWS_MAX_PATH && Platform.OS.isWindows()) {
private static Optional<File> shortenWindowsPath(File path) {
if (path != null && path.getPath().length() > WINDOWS_MAX_PATH && Platform.OS.isWindows()) {
// When spawning new processes on Windows, there is no uniform way
// to use long working directory paths that exceed the default path
// length limit, like for example using the raw path prefix '\\?\'
Expand All @@ -1042,12 +1068,12 @@ private static File shortenWindowsPath(File path) {
@SuppressWarnings("restriction")
String shortPath = org.eclipse.core.internal.filesystem.local.Win32Handler.getShortPathName(path.toString());
if (shortPath != null) {
return new File(shortPath);
return Optional.of(new File(shortPath));
} else {
log(Status.warning("Working directory of process to create exceeds Window's MAX_PATH limit and shortening the path failed.")); //$NON-NLS-1$
}
}
return path;
return Optional.ofNullable(path);
}

/**
Expand Down Expand Up @@ -1189,6 +1215,39 @@ private void initializeProcessFactories() {
}
}

private synchronized List<ExecFactoryFacade> getExecFactories() {
if (execFactories == null) {
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(DebugPlugin.PI_DEBUG_CORE, EXTENSION_POINT_EXEC_FACTORIES);
IConfigurationElement[] infos = extensionPoint.getConfigurationElements();
List<ExecFactoryFacade> list = new ArrayList<>();
for (IConfigurationElement configurationElement : infos) {
String clz = configurationElement.getAttribute("class"); //$NON-NLS-1$
if (clz != null) {
int priority;
String attribute = configurationElement.getAttribute("priority"); //$NON-NLS-1$
if (attribute == null) {
priority = 0;
}
try {
priority = Integer.parseInt(attribute);
} catch (NumberFormatException e) {
log(new Status(IStatus.ERROR, DebugPlugin.PI_DEBUG_CORE, ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_invalid_exec_factory, new Object[] {
configurationElement.getContributor().getName() }), null));
priority = 0;
}
list.add(new ExecFactoryFacade(configurationElement, priority));
} else {
String badDefiner = configurationElement.getContributor().getName();
log(new Status(IStatus.ERROR, DebugPlugin.PI_DEBUG_CORE, ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_invalid_exec_factory, new Object[] {
badDefiner }), null));
}
}
list.sort(Comparator.comparingInt(ExecFactoryFacade::getPriority).reversed());
execFactories = List.copyOf(list);
}
return execFactories;
}

private void invalidStatusHandler(Exception e, String id) {
log(new Status(IStatus.ERROR, DebugPlugin.PI_DEBUG_CORE, ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_5, new Object[] { id }), e));
}
Expand Down Expand Up @@ -1221,6 +1280,34 @@ public boolean equals(Object obj) {
}
}

private class ExecFactoryFacade implements ExecFactory {

private IConfigurationElement element;
private int priority;

ExecFactoryFacade(IConfigurationElement element, int priority) {
this.element = element;
this.priority = priority;
}

public int getPriority() {
return priority;
}

@Override
public Optional<Process> exec(String[] cmdLine, Optional<File> workingDirectory, Optional<Map<String, String>> environment, boolean mergeOutput) throws CoreException {
ExecFactory extension;
try {
extension = (ExecFactory) element.createExecutableExtension(IConfigurationElementConstants.CLASS);
} catch (CoreException e) {
log(e);
return Optional.empty();
}
return extension.exec(cmdLine, workingDirectory, environment, mergeOutput);
}

}

/**
* Executes runnables after event dispatch is complete.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2025 Christoph Läubrich and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.core;

import java.io.File;
import java.util.Map;
import java.util.Optional;

import org.eclipse.core.runtime.CoreException;

/**
* A {@link ExecFactory} can be used to control how Eclipse forks a new
* {@link Process}.
*
* @since 3.23
*/
public interface ExecFactory {

/**
* Executes the given command with the provided working directory and
* environment
*
* @param cmdLine the commandline to execute
* @param workingDirectory an optional working directory to be used
* otherwise the process factory must use its default
* @param environment the environment to use, if empty the process factory
* must use its defaults
* @param mergeOutput <code>true</code> if standard error and standard out
* should be merged
* @return an {@link Optional} describing the new process created, if an
* empty {@link Optional} is returned it is assumed that this
* factory is not capable of executing the provided command with the
* requested settings
* @throws CoreException if the factory is capable of execution but the
* creation of a process itself has failed
*/
Optional<Process> exec(String[] cmdLine, Optional<File> workingDirectory, Optional<Map<String, String>> environment, boolean mergeOutput) throws CoreException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class DebugCoreMessages extends NLS {
public static String DebugPlugin_2;
public static String DebugPlugin_3;
public static String DebugPlugin_4;
public static String DebugPlugin_invalid_exec_factory;
public static String DebugPlugin_invalid_exec_factory_priority;
public static String DebugPlugin_5;
public static String DebugPlugin_6;
public static String DebugPlugin_7;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ DebugPlugin_0=Exception occurred executing command line.
DebugPlugin_2=Internal message logged from Debug Core: {0}
DebugPlugin_3=Error logged from Debug Core:
DebugPlugin_4=Invalid process factory extension contributed by {0}; id: {1}
DebugPlugin_invalid_exec_factory=Invalid exec factory extension contributed by {0}, required class attribute is missing
DebugPlugin_invalid_exec_factory_priority=Invalid exec factory extension contributed by {0}, optional priority attribute is not a number
DebugPlugin_5=Invalid status handler extension: {0}
DebugPlugin_6=An exception occurred in asynchronous runnable.
DebugPlugin_7=An exception occurred while filtering debug events.
Expand Down
1 change: 1 addition & 0 deletions debug/org.eclipse.debug.core/plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ launchDelegatesExtensionPointName=Launch Delegates
breakpointImportParticipantsExtensionPoint.name = Breakpoint Import Participant
watchExpressionDelegatesName= Watch Expression Delegates
processFactoriesExtensionPointName=Process Factories
execFactoriesExtensionPointName=Exec Factories
logicalStructureTypesExtensionPointName=Logical Structure Types
logicalStructureProvidersExtensionPointName=Logical Structure Providers
stepFiltersExtensionPointName=Step Filters
Expand Down
1 change: 1 addition & 0 deletions debug/org.eclipse.debug.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<extension-point id="statusHandlers" name="%statusHandlerExtensionPointName" schema="schema/statusHandlers.exsd"/>
<extension-point id="watchExpressionDelegates" name="%watchExpressionDelegatesName" schema="schema/watchExpressionDelegates.exsd"/>
<extension-point id="processFactories" name="%processFactoriesExtensionPointName" schema="schema/processFactories.exsd"/>
<extension-point id="execFactories" name="%execFactoriesExtensionPointName" schema="schema/execFactories.exsd"/>
<extension-point id="logicalStructureTypes" name="%logicalStructureTypesExtensionPointName" schema="schema/logicalStructureTypes.exsd"/>
<extension-point id="sourceContainerTypes" name="%sourceContainerTypesName" schema="schema/sourceContainerTypes.exsd"/>
<extension-point id="sourcePathComputers" name="%sourcePathComputersName" schema="schema/sourcePathComputers.exsd"/>
Expand Down
Loading
Loading