blob: 7399bd968f3898a5293d18eff5730e5399b387a0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2019 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Mike Morearty - Bug 255310: Launching only gets the progress bar to 91%
*******************************************************************************/
package org.eclipse.debug.core.model;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.internal.core.DebugCoreMessages;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
/**
* Default implementation of a launch configuration delegate. Provides
* convenience methods for computing the build order of projects,
* building projects, and searching for errors in the workspace. The
* default pre-launch check prompts the user to launch in debug mode
* if breakpoints are present in the workspace.
* <p>
* Clients implementing launch configuration delegates should subclass
* this class.
* </p>
* @since 3.0
*/
public abstract class LaunchConfigurationDelegate implements ILaunchConfigurationDelegate2 {
/**
* Constant to define debug.core for the status codes
*
* @since 3.2
*/
private static final String DEBUG_CORE = "org.eclipse.debug.core"; //$NON-NLS-1$
/**
* Constant to define debug.ui for the status codes
*
* @since 3.2
*/
private static final String DEBUG_UI = "org.eclipse.debug.ui"; //$NON-NLS-1$
/**
* Status code for which a UI prompter is registered.
*/
protected static final IStatus promptStatus = new Status(IStatus.INFO, DEBUG_UI, 200, IInternalDebugCoreConstants.EMPTY_STRING, null);
/**
* Status code for which a prompter is registered to ask the user if they
* want to launch in debug mode when breakpoints are present.
*/
protected static final IStatus switchToDebugPromptStatus = new Status(IStatus.INFO, DEBUG_CORE, 201, IInternalDebugCoreConstants.EMPTY_STRING, null);
/**
* Status code for which a prompter is registered to ask the user if the
* want to continue launch despite existing compile errors
*/
protected static final IStatus complileErrorPromptStatus = new Status(IStatus.INFO, DEBUG_CORE, 202, IInternalDebugCoreConstants.EMPTY_STRING, null);
/**
* Status code for which a prompter will ask the user to save any/all of the dirty editors which have only to do
* with this launch (scoping them to the current launch/build)
*
* @since 3.2
*/
protected static final IStatus saveScopedDirtyEditors = new Status(IStatus.INFO, DEBUG_CORE, 222, IInternalDebugCoreConstants.EMPTY_STRING, null);
/**
* Status code for which a prompter is registered to ask the user if the
* want to continue launch despite existing compile errors in specific
* projects. This enhances the 'compileErrorPromptStatus' by specifying
* which projects the errors exist in.
*
* @since 3.1
*/
protected static final IStatus complileErrorProjectPromptStatus = new Status(IStatus.INFO, DEBUG_CORE, 203, IInternalDebugCoreConstants.EMPTY_STRING, null);
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException {
return null;
}
@Override
public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException {
IProject[] projects = getBuildOrder(configuration, mode);
if (projects == null) {
return true;
}
buildProjects(projects, SubMonitor.convert(monitor, 1));
return false;
}
/**
* Returns the projects to build before launching the given launch configuration
* or <code>null</code> if the entire workspace should be built incrementally.
* Subclasses should override as required.
*
* @param configuration the configuration being launched
* @param mode launch mode
* @return projects to build, in build order, or <code>null</code>
* @throws CoreException if an exception occurs
*/
protected IProject[] getBuildOrder(ILaunchConfiguration configuration, String mode) throws CoreException {
return null;
}
/**
* Returns the set of projects to use when searching for errors or <code>null</code>
* if no search is to be done.
*
* @param configuration the configuration being launched
* @param mode launch mode
* @return a list of projects or <code>null</code>
* @throws CoreException if an exception occurs
*/
protected IProject[] getProjectsForProblemSearch(ILaunchConfiguration configuration, String mode) throws CoreException {
return null;
}
@Override
public boolean finalLaunchCheck(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException {
monitor.beginTask("", 1); //$NON-NLS-1$
try {
IProject[] projects = getProjectsForProblemSearch(configuration, mode);
if (projects == null) {
return true; //continue launch
}
boolean continueLaunch = true;
monitor.subTask(DebugCoreMessages.LaunchConfigurationDelegate_6);
List<IAdaptable> errors = new ArrayList<>();
for (IProject project : projects) {
monitor.subTask(MessageFormat.format(DebugCoreMessages.LaunchConfigurationDelegate_7, new Object[] { project.getName() }));
if (existsProblems(project)) {
errors.add(project);
}
}
if (!errors.isEmpty()) {
errors.add(0, configuration);
IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
if (prompter != null) {
continueLaunch = ((Boolean) prompter.handleStatus(complileErrorProjectPromptStatus, errors)).booleanValue();
}
}
return continueLaunch;
} finally {
monitor.done();
}
}
/*
* If launching in run mode, and the configuration supports debug mode,
* check if there are any breakpoints in the workspace, and ask the user if
* they'd rather launch in debug mode. <p> Since 3.2, this check also
* performs saving of resources before launching. </p>
* @see
* org.eclipse.debug.core.model.ILaunchConfigurationDelegate2#preLaunchCheck
* (org.eclipse.debug.core.ILaunchConfiguration, java.lang.String,
* org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public boolean preLaunchCheck(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException {
if (!saveBeforeLaunch(configuration, mode, monitor)) {
return false;
}
if (mode.equals(ILaunchManager.RUN_MODE) && configuration.supportsMode(ILaunchManager.DEBUG_MODE)) {
IBreakpoint[] breakpoints= getBreakpoints(configuration);
if (breakpoints == null) {
return true;
}
for (IBreakpoint breakpoint : breakpoints) {
if (breakpoint.isEnabled()) {
IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
if (prompter != null) {
boolean launchInDebugModeInstead = ((Boolean)prompter.handleStatus(switchToDebugPromptStatus, configuration)).booleanValue();
if (launchInDebugModeInstead) {
return false; //kill this launch
}
}
// if no user prompt, or user says to continue (no need to check other breakpoints)
return true;
}
}
}
// no enabled breakpoints... continue launch
return true;
}
/**
* Performs the scoped saving of resources before launching and returns whether
* the launch should continue. By default, only resources contained within the projects
* which are part of the build scope are considered.
* <p>
* Subclasses may override this method if required.
* </p>
*
* @param configuration the configuration being launched
* @param mode the launch mode
* @param monitor progress monitor
* @return whether the launch should continue
* @throws CoreException if an exception occurs during the save
* @since 3.2
*/
protected boolean saveBeforeLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException {
monitor.beginTask("", 1); //$NON-NLS-1$
try {
IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
if(prompter != null) {
//do save here and remove saving from DebugUIPlugin to avoid it 'trumping' this save
IProject[] buildOrder = getBuildOrder(configuration, mode);
if(!((Boolean)prompter.handleStatus(saveScopedDirtyEditors, new Object[]{configuration, buildOrder})).booleanValue()) {
return false;
}
}
return true;
} finally {
monitor.done();
}
}
/**
* Returns the breakpoint collection that is relevant for this launch delegate.
* By default this is all the breakpoints registered with the Debug breakpoint manager.
*
* @param configuration the configuration to get associated breakpoints for
* @since 3.1
* @return the breakpoints that are relevant for this launch delegate
*/
protected IBreakpoint[] getBreakpoints(ILaunchConfiguration configuration) {
IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
if (!breakpointManager.isEnabled()) {
// no need to check breakpoints individually.
return null;
}
return breakpointManager.getBreakpoints();
}
/**
* Returns an array of projects in their suggested build order
* containing all of the projects specified by <code>baseProjects</code>
* and all of their referenced projects.
*
* @param baseProjects a collection of projects
* @return an array of projects in their suggested build order
* containing all of the projects specified by <code>baseProjects</code>
* @throws CoreException if an error occurs while computing referenced
* projects
*/
protected IProject[] computeReferencedBuildOrder(IProject[] baseProjects) throws CoreException {
HashSet<IProject> unorderedProjects = new HashSet<>();
for (IProject baseProject : baseProjects) {
unorderedProjects.add(baseProject);
addReferencedProjects(baseProject, unorderedProjects);
}
IProject[] projectSet = unorderedProjects.toArray(new IProject[unorderedProjects.size()]);
return computeBuildOrder(projectSet);
}
/**
* Adds all projects referenced by <code>project</code> to the given
* set.
*
* @param project project
* @param references set to which referenced projects are added
* @throws CoreException if an error occurs while computing referenced
* projects
*/
protected void addReferencedProjects(IProject project, Set<IProject> references) throws CoreException {
if (project.isOpen()) {
for (IProject refProject : project.getReferencedProjects()) {
if (refProject.exists() && !references.contains(refProject)) {
references.add(refProject);
addReferencedProjects(refProject, references);
}
}
}
}
/**
* Returns a list of projects in their suggested build order from the
* given unordered list of projects.
*
* @param projects the list of projects to sort into build order
* @return a new array containing all projects from <code>projects</code> sorted
* according to their build order.
*/
protected IProject[] computeBuildOrder(IProject[] projects) {
String[] orderedNames = ResourcesPlugin.getWorkspace().getDescription().getBuildOrder();
if (orderedNames != null) {
List<IProject> orderedProjects = new ArrayList<>(projects.length);
//Projects may not be in the build order but should be built if selected
List<IProject> unorderedProjects = new ArrayList<>(projects.length);
Collections.addAll(unorderedProjects, projects);
for (String projectName : orderedNames) {
for (Iterator<IProject> iterator = unorderedProjects.iterator(); iterator.hasNext();) {
IProject project = iterator.next();
if (project.getName().equals(projectName)) {
orderedProjects.add(project);
iterator.remove();
break;
}
}
}
//Add anything not specified before we return
orderedProjects.addAll(unorderedProjects);
return orderedProjects.toArray(new IProject[orderedProjects.size()]);
}
// Computing build order returned null, try the project prerequisite order
IWorkspace.ProjectOrder po = ResourcesPlugin.getWorkspace().computeProjectOrder(projects);
return po.projects;
}
/**
* Returns whether the given project contains any problem markers of the
* specified severity.
*
* @param proj the project to search
* @return whether the given project contains any problems that should
* stop it from launching
* @throws CoreException if an error occurs while searching for
* problem markers
*/
protected boolean existsProblems(IProject proj) throws CoreException {
IMarker[] markers = proj.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
if (markers.length > 0) {
for (IMarker marker : markers) {
if (isLaunchProblem(marker)) {
return true;
}
}
}
return false;
}
/**
* Returns whether the given problem should potentially abort the launch.
* By default if the problem has an error severity, the problem is considered
* a potential launch problem. Subclasses may override to specialize error
* detection.
*
* @param problemMarker candidate problem
* @return whether the given problem should potentially abort the launch
* @throws CoreException if any exceptions occur while accessing marker attributes
*/
protected boolean isLaunchProblem(IMarker problemMarker) throws CoreException {
Integer severity = (Integer)problemMarker.getAttribute(IMarker.SEVERITY);
if (severity != null) {
return severity.intValue() >= IMarker.SEVERITY_ERROR;
}
return false;
}
/**
* Performs an incremental build on each of the given projects.
*
* @param projects projects to build
* @param monitor progress monitor
* @throws CoreException if an exception occurs while building
*/
protected void buildProjects(final IProject[] projects, IProgressMonitor monitor) throws CoreException {
IWorkspaceRunnable build = pm -> {
SubMonitor localmonitor = SubMonitor.convert(pm, DebugCoreMessages.LaunchConfigurationDelegate_scoped_incremental_build, projects.length);
try {
for (IProject project : projects) {
if (localmonitor.isCanceled()) {
throw new OperationCanceledException();
}
project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, localmonitor.newChild(1));
}
} finally {
localmonitor.done();
}
};
ResourcesPlugin.getWorkspace().run(build, monitor);
}
}