blob: 83b25f5470f491c831758177e11509143ea8ac10 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Oak Ridge National Laboratory 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:
* John Eblen - initial implementation
*******************************************************************************/
package org.eclipse.ptp.internal.rdt.sync.cdt.core;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.ErrorParserManager;
import org.eclipse.cdt.core.ICommandLauncher;
import org.eclipse.cdt.core.IConsoleParser;
import org.eclipse.cdt.core.language.settings.providers.ICBuildOutputParser;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsEditableProvider;
import org.eclipse.cdt.core.language.settings.providers.IWorkingDirectoryTracker;
import org.eclipse.cdt.core.model.ILanguage;
import org.eclipse.cdt.core.model.LanguageManager;
import org.eclipse.cdt.core.resources.IConsole;
import org.eclipse.cdt.core.settings.model.CIncludePathEntry;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.internal.core.BuildRunnerHelper;
import org.eclipse.cdt.managedbuilder.core.IToolChain;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedMakeMessages;
import org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector;
import org.eclipse.cdt.utils.CommandLineUtil;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ptp.internal.rdt.sync.cdt.core.messages.Messages;
import org.eclipse.ptp.internal.rdt.sync.cdt.core.remotemake.SyncCommandLauncher;
import org.eclipse.ptp.rdt.sync.core.SyncConfig;
import org.eclipse.ptp.rdt.sync.core.SyncConfigManager;
import org.eclipse.ptp.rdt.sync.core.exceptions.MissingConnectionException;
import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.remote.core.IRemoteFileService;
/**
* Language settings provider to detect built-in compiler settings for GCC compiler, modified to work with synchronized projects.
* The goal of this class is to rely on superclasses as much as possible and minimize code copying while modifying the code to:
* 1) Run processes on the remote machine for the current build configuration
* 2) Convert paths to UNC notation with connection name prepended:
* syntax: //<connection name>/<discovered path>
*/
public class SyncGCCBuiltinSpecsDetector extends GCCBuiltinSpecsDetector implements ILanguageSettingsEditableProvider {
private static final String CDT_MANAGEDBUILDER_UI_PLUGIN_ID = "org.eclipse.cdt.managedbuilder.ui"; //$NON-NLS-1$
private static final String SCANNER_DISCOVERY_CONSOLE = "org.eclipse.cdt.managedbuilder.ScannerDiscoveryConsole"; //$NON-NLS-1$
private static final String SCANNER_DISCOVERY_GLOBAL_CONSOLE = "org.eclipse.cdt.managedbuilder.ScannerDiscoveryGlobalConsole"; //$NON-NLS-1$
private static final String DEFAULT_CONSOLE_ICON = "icons/obj16/inspect_sys.gif"; //$NON-NLS-1$
private static final String GMAKE_ERROR_PARSER_ID = "org.eclipse.cdt.core.GmakeErrorParser"; //$NON-NLS-1$
private static final String PLUGIN_ID = "org.eclipse.ptp.internal.rdt.sync.cdt.core"; //$NON-NLS-1$
private static final String TOOLCHAIN_EXTENSION_POINT_ID = "ToolchainPath"; //$NON-NLS-1$
private static final int MONITOR_SCALE = 100;
private static final int TICKS_OUTPUT_PARSING = 1 * MONITOR_SCALE;
private static final int TICKS_EXECUTE_COMMAND = 1 * MONITOR_SCALE;
private final AtomicBoolean isBeingExecuted = new AtomicBoolean(false);
// Indicate whether the spec file has been created or verified to exist. This may be false on project startup before project is
// fully initialized or become false if there are problems connecting to and running commands on the remote machine. Checking
// this variable before execution prevents spurious error messages.
private boolean specFileExists = false;
/**
* Internal ICConsoleParser to handle individual run for one language.
* This is basically a copy of: {@link
* org.eclipse.cdt.managedbuilder.language.settings.providers.AbstractBuiltinSpecsDetector.ConsoleParserAdapter()} necessary
* because that class is private.
*/
private class ConsoleParserAdapter implements ICBuildOutputParser {
@Override
public void startup(ICConfigurationDescription cfgDescription, IWorkingDirectoryTracker cwdTracker) throws CoreException {
SyncGCCBuiltinSpecsDetector.this.cwdTracker = cwdTracker;
}
@Override
public boolean processLine(String line) {
return SyncGCCBuiltinSpecsDetector.this.processLine(line);
}
@Override
public void shutdown() {
SyncGCCBuiltinSpecsDetector.this.cwdTracker = null;
}
}
/**
* A copy of: {@link org.eclipse.cdt.managedbuilder.language.settings.providers.AbstractBuiltinSpecsDetector#runForLanguage()}
* modified to use the sync command launcher and to not run if spec file is null (see code comments). Note that this method is
* called by "runForLanguage," it does not override it. Thus, all of the setup for running is done twice. Specifically, a
* BuildRunnerHelper is built twice. Ideally, CDT would provide an extension point to change the command launcher, as it does
* for
* builds.
*
* @return ICommandLauncher status of run
*/
@Override
protected int runProgramForLanguage(String languageId, String command, String[] envp, URI workingDirectoryURI,
OutputStream consoleOut, OutputStream consoleErr, IProgressMonitor monitor) throws CoreException, IOException {
BuildRunnerHelper buildRunnerHelper = new BuildRunnerHelper(currentProject);
if (monitor == null) {
monitor = new NullProgressMonitor();
}
int retval = ICommandLauncher.COMMAND_CANCELED;
boolean closeAttempted = false;
try {
monitor.beginTask(ManagedMakeMessages.getFormattedString(Messages.SyncGCCBuiltinSpecsDetector_0, getName()),
TICKS_EXECUTE_COMMAND + TICKS_OUTPUT_PARSING);
if (!isBeingExecuted.compareAndSet(false, true)) {
return retval;
}
if (!specFileExists) {
return retval;
}
IConsole console;
if (super.isConsoleEnabled()) {
console = startProviderConsole();
} else {
// that looks in extension points registry and won't find the id, this console is not shown
console = CCorePlugin.getDefault().getConsole(ManagedBuilderCorePlugin.PLUGIN_ID + ".console.hidden"); //$NON-NLS-1$
}
console.start(currentProject);
SyncCommandLauncher launcher = new SyncCommandLauncher();
launcher.setSyncBeforeRun(false);
launcher.setSyncAfterRun(false);
launcher.setProject(currentProject);
IPath program = new Path(""); //$NON-NLS-1$
String[] args = new String[0];
String[] cmdArray = CommandLineUtil.argumentsToArray(command);
String toolchainPath = getToolchainPath(currentProject);
if (toolchainPath != null) {
cmdArray[0] = toolchainPath + cmdArray[0];
}
if (cmdArray != null && cmdArray.length > 0) {
program = new Path(cmdArray[0]);
if (cmdArray.length > 1) {
args = new String[cmdArray.length - 1];
System.arraycopy(cmdArray, 1, args, 0, args.length);
}
}
// Using GMAKE_ERROR_PARSER_ID as it can handle generated error messages
// TODO: Add marker generator - superclass marker generator is private.
ErrorParserManager epm = new ErrorParserManager(currentProject, buildDirURI, null,
new String[] { GMAKE_ERROR_PARSER_ID });
ConsoleParserAdapter consoleParser = new ConsoleParserAdapter();
consoleParser.startup(currentCfgDescription, epm);
List<IConsoleParser> parsers = new ArrayList<IConsoleParser>();
parsers.add(consoleParser);
buildRunnerHelper.setLaunchParameters(launcher, program, args, currentProject.getLocationURI(), envp);
buildRunnerHelper.prepareStreams(epm, parsers, console, new SubProgressMonitor(monitor, TICKS_OUTPUT_PARSING));
buildRunnerHelper.greeting(ManagedMakeMessages.getFormattedString(Messages.SyncGCCBuiltinSpecsDetector_1, getName()));
retval = buildRunnerHelper.build(new SubProgressMonitor(monitor, TICKS_EXECUTE_COMMAND,
SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
closeAttempted = true;
buildRunnerHelper.close();
buildRunnerHelper.goodbye();
} catch (Exception e) {
if (closeAttempted) {
ManagedBuilderCorePlugin.log(e);
} else {
ManagedBuilderCorePlugin.log(new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID,
Messages.SyncGCCBuiltinSpecsDetector_2, e)));
try {
buildRunnerHelper.close();
} catch (IOException e1) {
ManagedBuilderCorePlugin.log(e1);
}
}
} finally {
monitor.done();
isBeingExecuted.set(false);
}
return retval;
}
/**
* This method intercepts and modifies the scanner discovery entries. It changes include paths to UNC notation with the correct
* connection name prepended.
*/
@Override
protected void setSettingEntries(List<? extends ICLanguageSettingEntry> entries) {
if (entries == null) {
super.setSettingEntries(entries);
return;
}
SyncConfig config = SyncConfigManager.getActive(currentProject);
if (config.getSyncProviderId() == null) {
// For local configurations, no special processing is needed.
super.setSettingEntries(entries);
return;
}
IRemoteConnection conn = null;
try {
conn = config.getRemoteConnection();
} catch (MissingConnectionException e1) {
// Impossible to build includes properly without connection name
super.setSettingEntries(entries);
return;
}
List<ICLanguageSettingEntry> newEntries = new ArrayList<ICLanguageSettingEntry>();
for (ICLanguageSettingEntry entry : entries) {
if ((entry instanceof CIncludePathEntry) && ((entry.getFlags() & ICSettingEntry.VALUE_WORKSPACE_PATH) == 0)) {
String oldPath = ((CIncludePathEntry) entry).getValue();
// Bug 402350: Sync scanner discovery has corrupt remote paths when using Windows
if (oldPath.startsWith("C:")) { //$NON-NLS-1$
oldPath = oldPath.substring(2);
}
String newPath = "//" + conn.getName() + oldPath; //$NON-NLS-1$
ICLanguageSettingEntry newEntry = new CIncludePathEntry(newPath, entry.getFlags());
newEntries.add(newEntry);
} else {
newEntries.add(entry);
}
}
super.setSettingEntries(newEntries);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.cdt.managedbuilder.language.settings.providers.AbstractBuiltinSpecsDetector#getSpecFile(java.lang.String)
*/
@Override
protected String getSpecFile(String languageId) {
// These checks are only necessary for synchronized projects, since file location is dependent on the current project and
// current sync configuration.
specFileExists = false;
if (currentProject == null) {
return null;
}
SyncConfig config = SyncConfigManager.getActive(currentProject);
if (config == null) {
return null;
}
// Build spec file name
String specFileName = SPEC_FILE_BASE;
String ext = getSpecFileExtension(languageId);
if (ext != null) {
specFileName = specFileName + '.' + ext;
}
IPath workingLocation = new Path(config.getLocation(currentProject));
// TODO: Get rid of .ptp-sync string literal.
// TODO: What if .ptp-sync does not exist?
// TODO: What if remote system does not use '/' path separator? (assumed by IPath.toString())
IPath fileLocation = workingLocation.append(".ptp-sync").append(specFileName); //$NON-NLS-1$
// Create spec file if it doesn't exist
IRemoteConnection conn = null;
try {
conn = config.getRemoteConnection();
} catch (MissingConnectionException e1) {
return fileLocation.toString();
}
final IRemoteFileService fileService = conn.getService(IRemoteFileService.class);
if (fileService == null) {
return null;
}
final IFileStore fileStore = fileService.getResource(fileLocation.toString());
final IFileInfo fileInfo = fileStore.fetchInfo();
if (!fileInfo.exists()) {
OutputStream os;
try {
os = fileStore.openOutputStream(EFS.NONE, null);
os.write('\n');
os.close();
} catch (CoreException e) {
Activator.log(e);
} catch (IOException e) {
Activator.log(e);
}
}
specFileExists = true;
return fileLocation.toString();
}
/**
* Create and start the provider console.
* This is basically a copy of:
* {@link org.eclipse.cdt.managedbuilder.language.settings.providers.AbstractBuiltinSpecsDetector#startProviderConsole()}
* necessary because that class is private.
*
* @return CDT console.
*/
private IConsole startProviderConsole() {
IConsole console = null;
if (super.isConsoleEnabled() && currentLanguageId != null) {
String extConsoleId;
if (currentProject != null) {
extConsoleId = SCANNER_DISCOVERY_CONSOLE;
} else {
extConsoleId = SCANNER_DISCOVERY_GLOBAL_CONSOLE;
}
ILanguage ld = LanguageManager.getInstance().getLanguage(currentLanguageId);
if (ld != null) {
String consoleId = ManagedBuilderCorePlugin.PLUGIN_ID + '.' + getId() + '.' + currentLanguageId;
String consoleName = getName() + ", " + ld.getName(); //$NON-NLS-1$
URL defaultIcon = Platform.getBundle(CDT_MANAGEDBUILDER_UI_PLUGIN_ID).getEntry(DEFAULT_CONSOLE_ICON);
if (defaultIcon == null) {
String msg = Messages.SyncGCCBuiltinSpecsDetector_3 + DEFAULT_CONSOLE_ICON
+ Messages.SyncGCCBuiltinSpecsDetector_4 + CDT_MANAGEDBUILDER_UI_PLUGIN_ID;
ManagedBuilderCorePlugin.log(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.PLUGIN_ID, msg));
}
console = CCorePlugin.getDefault().getConsole(extConsoleId, consoleId, consoleName, defaultIcon);
}
}
if (console == null) {
// that looks in extension points registry and won't find the id, this console is not shown
console = CCorePlugin.getDefault().getConsole(ManagedBuilderCorePlugin.PLUGIN_ID + ".console.hidden"); //$NON-NLS-1$
}
return console;
}
private String getToolchainPath(IProject project) {
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(PLUGIN_ID, TOOLCHAIN_EXTENSION_POINT_ID);
// Bug 434896
if (extensionPoint == null) {
return null;
}
IConfigurationElement[] infos = extensionPoint.getConfigurationElements();
for (IConfigurationElement configurationElement : infos) {
if (configurationElement.getName().equals("mapping")) { //$NON-NLS-1$
IToolChain toolChain = ManagedBuildManager.getBuildInfo(project).getDefaultConfiguration().getToolChain();
if (toolChain != null) {
String toolchainName = toolChain.getUniqueRealName();
if (configurationElement.getAttribute("toolchain").equals(toolchainName)) { //$NON-NLS-1$
return configurationElement.getAttribute("path"); //$NON-NLS-1$
}
}
}
}
return null;
}
}