/*******************************************************************************
* Copyright (c) 2017 Rogue Wave Software Inc. and others.
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Rogue Wave Software Inc. - initial implementation
*******************************************************************************/
package org.eclipse.php.phpunit.ui.launch;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersInitializer;
import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersKeys;
import org.eclipse.php.internal.debug.core.IPHPDebugConstants;
import org.eclipse.php.internal.debug.core.Logger;
import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages;
import org.eclipse.php.internal.debug.core.PHPDebugPlugin;
import org.eclipse.php.internal.debug.core.launching.DebugSessionIdGenerator;
import org.eclipse.php.internal.debug.core.launching.PHPLaunchUtilities;
import org.eclipse.php.internal.debug.core.preferences.PHPProjectPreferences;
import org.eclipse.php.internal.debug.core.preferences.PHPexeItem;
import org.eclipse.php.internal.debug.core.preferences.PHPexes;
import org.eclipse.php.internal.debug.core.zend.communication.DebuggerCommunicationDaemon;
import org.eclipse.php.internal.debug.core.zend.debugger.DebugParametersInitializersRegistry;
import org.eclipse.php.internal.debug.core.zend.debugger.PHPSessionLaunchMapper;
import org.eclipse.php.internal.debug.core.zend.debugger.ProcessCrashDetector;
import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerSettingsUtil;
import org.eclipse.php.internal.debug.daemon.DaemonPlugin;
import org.eclipse.php.phpunit.PHPUnitPlugin;
import org.eclipse.swt.widgets.Display;
import com.ibm.icu.text.MessageFormat;
@SuppressWarnings("restriction")
public class PHPUnitZDLauncher extends PHPUnitBasicLauncher {
class CrashDetector extends ProcessCrashDetector {
private ILaunch launch;
public CrashDetector(ILaunch launch, Process p) {
super(launch, p);
this.launch = launch;
}
@Override
public void run() {
super.run();
/*
* In case this thread ended and we do not have any IDebugTarget
* (PHPDebugTarget) hooked in the launch that was created, we can
* tell that there is something wrong, and probably there is no
* debugger installed (e.g. the debugger dll/so is not properly
* configured in the php.ini).
*/
if (launch != null && launch.getDebugTarget() == null) {
String launchName = launch.getLaunchConfiguration().getName();
boolean isRunMode = ILaunchManager.RUN_MODE.equals(launch.getLaunchMode());
String msg = null;
if (isRunMode) {
msg = MessageFormat.format(PHPDebugCoreMessages.Debugger_Error_Message_3,
new Object[] { launchName });
} else {
msg = MessageFormat.format(PHPDebugCoreMessages.Debugger_Error_Message_2,
new Object[] { launchName });
}
final String message = msg;
Display.getDefault().asyncExec(() -> {
MessageDialog.openWarning(Display.getDefault().getActiveShell(),
PHPDebugCoreMessages.Debugger_Launch_Error, message);
DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch);
});
}
}
}
public PHPUnitZDLauncher(ILaunchConfiguration configuration, ILaunch launch, PHPUnitOptionsList optionsList) {
super(configuration, launch, optionsList);
}
@Override
protected void launchDebugMode(String fileName, File workingDir, String phpExeString, IProject project,
Map<String, String> envVariables, IProgressMonitor monitor) throws CoreException {
launchDebugOrProfileMode(fileName, workingDir, phpExeString, project, envVariables, monitor);
}
@Override
protected void launchProfileMode(String fileName, File workingDir, String phpExeString, IProject project,
Map<String, String> envVariables, IProgressMonitor monitor) throws CoreException {
launchDebugOrProfileMode(fileName, workingDir, phpExeString, project, envVariables, monitor);
}
private void launchDebugOrProfileMode(String fileName, File workingDir, String phpExeString, IProject project,
Map<String, String> envVariables, IProgressMonitor monitor) throws CoreException {
PHPexeItem phpExeItem = PHPLaunchUtilities.getPHPExe(configuration);
int requestPort = getDebugPort(phpExeItem);
// Check that the debug daemon is running
if (!PHPLaunchUtilities.isDebugDaemonActive(requestPort, DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID)) {
PHPLaunchUtilities.showLaunchErrorMessage(NLS.bind(
PHPDebugCoreMessages.ExeLaunchConfigurationDelegate_PortInUse, requestPort, phpExeItem.getName()));
monitor.setCanceled(true);
monitor.done();
return;
}
boolean stopAtFirstLine = configuration.getAttribute(IDebugParametersKeys.FIRST_LINE_BREAKPOINT,
PHPProjectPreferences.getStopAtFirstLine(project));
ILaunchConfigurationWorkingCopy wc;
if (configuration.isWorkingCopy()) {
wc = (ILaunchConfigurationWorkingCopy) configuration;
} else {
wc = configuration.getWorkingCopy();
}
// Set Project Name
if (project != null) {
wc.setAttribute(IPHPDebugConstants.PHP_Project, project.getFullPath().toString());
}
// Set transfer encoding:
wc.setAttribute(IDebugParametersKeys.TRANSFER_ENCODING, PHPProjectPreferences.getTransferEncoding(project));
wc.setAttribute(IDebugParametersKeys.OUTPUT_ENCODING, PHPProjectPreferences.getOutputEncoding(project));
wc.setAttribute(IDebugParametersKeys.PHP_DEBUG_TYPE, IDebugParametersKeys.PHP_EXE_SCRIPT_DEBUG);
wc.doSave();
if (monitor.isCanceled()) {
return;
}
// Generate a session id for this launch and put it in the map
int sessionID = DebugSessionIdGenerator.generateSessionID();
PHPSessionLaunchMapper.put(sessionID, launch);
// Define all needed debug attributes:
launch.setAttribute(IDebugParametersKeys.PORT, Integer.toString(requestPort));
launch.setAttribute(IDebugParametersKeys.FIRST_LINE_BREAKPOINT, Boolean.toString(stopAtFirstLine));
launch.setAttribute(IDebugParametersKeys.SESSION_ID, Integer.toString(sessionID));
// Trigger the session by initiating a debug request to the php.exe
try {
launch.setAttribute(IDebugParametersKeys.EXECUTABLE_LAUNCH, Boolean.toString(true));
IDebugParametersInitializer parametersInitializer = DebugParametersInitializersRegistry
.getBestMatchDebugParametersInitializer(launch);
String query = PHPLaunchUtilities.generateQuery(launch, parametersInitializer);
String iniFileLocation = launch.getAttribute(IDebugParametersKeys.PHP_INI_LOCATION);
File phpExeFile = new File(phpExeString);
// Determine configuration file directory:
String phpConfigDir = phpExeFile.getParent();
if (iniFileLocation != null && !iniFileLocation.isEmpty()) {
phpConfigDir = new File(iniFileLocation).getParent();
}
// Detect PHP SAPI type:
String sapiType = null;
PHPexeItem[] items = PHPexes.getInstance().getAllItems();
for (PHPexeItem item : items) {
if (item.getExecutable().equals(phpExeFile)) {
sapiType = item.getSapiType();
break;
}
}
String[] args = PHPLaunchUtilities.getProgramArguments(launch.getLaunchConfiguration());
// Prepare the environment
Map<String, String> additionalLaunchEnvironment = PHPLaunchUtilities.getPHPCGILaunchEnvironment(fileName,
query, phpConfigDir, phpExeFile.getParent(), sapiType == PHPexeItem.SAPI_CGI ? args : null);
if (envVariables == null) {
envVariables = additionalLaunchEnvironment;
} else {
additionalLaunchEnvironment.putAll(envVariables);
envVariables = additionalLaunchEnvironment;
}
String[] environmetVars = PHPLaunchUtilities.getEnvironment(launch.getLaunchConfiguration(),
asAttributesArray(envVariables));
// Prepare the command line.
String[] phpCmdArray = getCommandLine(project, phpExeString, phpConfigDir, fileName,
sapiType == PHPexeItem.SAPI_CLI ? args : null);
List<String> allArgs = new ArrayList<>();
allArgs.addAll(Arrays.asList(phpCmdArray));
allArgs.addAll(optionsList.getList());
phpCmdArray = allArgs.toArray(new String[0]);
// Make sure that we have executable permissions on the file.
PHPexes.changePermissions(new File(phpCmdArray[0]));
if (PHPDebugPlugin.DEBUG) {
System.out.println("Executing: " + Arrays.toString(phpCmdArray)); //$NON-NLS-1$
System.out.println("Process environment: " //$NON-NLS-1$
+ Arrays.toString(environmetVars));
}
DaemonPlugin.getDefault().makeSureDebuggerInitialized(null);
// Execute the command line.
Process p = Runtime.getRuntime().exec(phpCmdArray, environmetVars, workingDir);
// Attach a crash detector
new Thread(new CrashDetector(launch, p)).start();
} catch (java.io.IOException e1) {
Logger.logException("PHPDebugTarget: Debugger didn't find file to debug.", e1); //$NON-NLS-1$
String errorMessage = PHPDebugCoreMessages.DebuggerFileNotFound_1;
throw new DebugException(new Status(IStatus.ERROR, PHPDebugPlugin.getID(),
IPHPDebugConstants.INTERNAL_ERROR, errorMessage, e1));
} catch (CoreException e) {
PHPUnitPlugin.log(e);
}
}
/**
* @param phpExe
* @return debug port for given phpExe
* @throws CoreException
*/
protected int getDebugPort(PHPexeItem phpExe) throws CoreException {
int customRequestPort = ZendDebuggerSettingsUtil.getDebugPort(phpExe.getUniqueId());
if (customRequestPort != -1) {
return customRequestPort;
}
return PHPDebugPlugin.getDebugPort(DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID);
}
private String[] asAttributesArray(Map<String, String> attributesMap) {
String[] attributes = new String[attributesMap.size()];
int index = 0;
for (Map.Entry<String, String> entry : attributesMap.entrySet()) {
attributes[index++] = entry.getKey() + '=' + entry.getValue();
}
return attributes;
}
}