diff options
author | Jonah Graham | 2018-02-12 16:18:32 +0000 |
---|---|---|
committer | Jonah Graham | 2018-02-12 17:31:31 +0000 |
commit | 2c82259f953af0608926071f9f46298e550675df (patch) | |
tree | 93ff53f576e140f48cac2c8e7a95c25f3bf0fa0a /core/org.eclipse.cdt.core/src/org/eclipse | |
parent | fadd0a50156e36faae91d9bf185252e7898c8375 (diff) | |
download | org.eclipse.cdt-2c82259f953af0608926071f9f46298e550675df.tar.gz org.eclipse.cdt-2c82259f953af0608926071f9f46298e550675df.tar.xz org.eclipse.cdt-2c82259f953af0608926071f9f46298e550675df.zip |
Fix newlines.
Change-Id: I78acc1c843d58a5e1f69db6ea68a6c6dda34198b
Diffstat (limited to 'core/org.eclipse.cdt.core/src/org/eclipse')
-rw-r--r-- | core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/BuildRunnerHelper.java | 1230 |
1 files changed, 615 insertions, 615 deletions
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/BuildRunnerHelper.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/BuildRunnerHelper.java index 7a6f76ff78d..d3ac8b46ea7 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/BuildRunnerHelper.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/BuildRunnerHelper.java @@ -1,615 +1,615 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2016 Andrew Gvozdev 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:
- * Andrew Gvozdev - initial API and implementation
- * IBM Corporation
- *******************************************************************************/
-
-package org.eclipse.cdt.internal.core;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URI;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.TimeUnit;
-
-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.envvar.IEnvironmentVariable;
-import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager;
-import org.eclipse.cdt.core.model.ICModelMarker;
-import org.eclipse.cdt.core.resources.IConsole;
-import org.eclipse.cdt.core.resources.RefreshScopeManager;
-import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
-import org.eclipse.cdt.internal.errorparsers.FixitManager;
-import org.eclipse.cdt.utils.EFSExtensionManager;
-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.Assert;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.QualifiedName;
-
-/**
- * Helper class attempting to unify interactions with build console,
- * such as style of console output and handling of console output parsers.
- *
- * As of CDT 8.1, this class is experimental, internal and work in progress.
- * <strong>API is unstable and subject to change.</strong>
- */
-public class BuildRunnerHelper implements Closeable {
- private static final String PROGRESS_MONITOR_QUALIFIER = CCorePlugin.PLUGIN_ID + ".progressMonitor"; //$NON-NLS-1$
- private static final int PROGRESS_MONITOR_SCALE = 100;
- private static final int TICKS_STREAM_PROGRESS_MONITOR = 1 * PROGRESS_MONITOR_SCALE;
- private static final int TICKS_EXECUTE_PROGRAM = 1 * PROGRESS_MONITOR_SCALE;
- private static final int TICKS_PARSE_OUTPUT = 1 * PROGRESS_MONITOR_SCALE;
-
- private IProject project;
-
- private IConsole console = null;
- private ErrorParserManager errorParserManager = null;
- private StreamProgressMonitor streamProgressMonitor = null;
- private OutputStream stdout = null;
- private OutputStream stderr = null;
- private OutputStream consoleOut = null;
- private OutputStream consoleInfo = null;
-
- private long startTime = 0;
- private long endTime = 0;
-
- private QualifiedName progressPropertyName = null;
-
- private ICommandLauncher launcher;
- private IPath buildCommand;
- private String[] args;
- private URI workingDirectoryURI;
- String[] envp;
-
- private boolean isStreamsOpen = false;
- boolean isCancelled = false;
-
- /**
- * Constructor.
- */
- public BuildRunnerHelper(IProject project) {
- this.project = project;
- }
-
- /**
- * Set parameters for the launch.
- * @param envp - String[] array of environment variables in format "var=value" suitable for using
- * as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir)
- */
- public void setLaunchParameters(ICommandLauncher launcher, IPath buildCommand, String[] args, URI workingDirectoryURI, String[] envp) {
- this.launcher = launcher;
- launcher.setProject(project);
- // Print the command for visual interaction.
- launcher.showCommand(true);
-
- this.buildCommand = buildCommand;
- this.args = args;
- this.workingDirectoryURI = workingDirectoryURI;
- this.envp = envp;
- }
-
- /**
- * Open and set up streams for use by {@link BuildRunnerHelper}.
- * This must be followed by {@link #close()} to close the streams. Use try...finally for that.
- *
- * @param epm - ErrorParserManger for error parsing and coloring errors on the console
- * @param buildOutputParsers - list of console output parsers or {@code null}.
- * @param con - the console.
- * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)}
- * has not been called yet.
- * @throws CoreException
- */
- public void prepareStreams(ErrorParserManager epm, List<IConsoleParser> buildOutputParsers, IConsole con, IProgressMonitor monitor) throws CoreException {
- errorParserManager = epm;
- console = con;
-
- // Visualize the flow of the streams:
- //
- // console <- EPM
- // ^
- // IConsoleParsers (includes EPM + other parsers)
- // ^
- // null <- StreamMomitor <= Sniffer <= Process (!!! the flow starts here!)
- //
-
- isStreamsOpen = true;
-
- consoleOut = console.getOutputStream();
- // stdout/stderr get to the console through ErrorParserManager
- errorParserManager.setOutputStream(consoleOut);
-
- List<IConsoleParser> parsers = new ArrayList<IConsoleParser>();
- // Using ErrorParserManager as console parser helps to avoid intermixing buffered streams
- // as ConsoleOutputSniffer waits for EOL to send a line to console parsers
- // separately for each stream.
- parsers.add(errorParserManager);
- if (buildOutputParsers != null) {
- parsers.addAll(buildOutputParsers);
- }
-
- Integer lastWork = null;
- if (buildCommand != null && project != null) {
- progressPropertyName = getProgressPropertyName(buildCommand, args);
- lastWork = (Integer)project.getSessionProperty(progressPropertyName);
- }
- if (lastWork == null) {
- lastWork = TICKS_STREAM_PROGRESS_MONITOR;
- }
-
- streamProgressMonitor = new StreamProgressMonitor(monitor, null, lastWork.intValue());
- ConsoleOutputSniffer sniffer = new ConsoleOutputSniffer(streamProgressMonitor, streamProgressMonitor, parsers.toArray(new IConsoleParser[parsers.size()]));
- stdout = sniffer.getOutputStream();
- stderr = sniffer.getErrorStream();
- }
-
- /**
- * @return the output stream to connect stdout of a process
- */
- public OutputStream getOutputStream() {
- return stdout;
- }
-
- /**
- * @return the output stream to connect stderr of a process
- */
- public OutputStream getErrorStream() {
- return stderr;
- }
-
- /**
- * Remove problem markers created for the resource by previous build.
- *
- * @param rc - resource to remove its markers.
- * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)}
- * has not been called yet.
- * @throws CoreException
- */
- public void removeOldMarkers(IResource rc, IProgressMonitor monitor) throws CoreException {
- if (monitor == null) {
- monitor = new NullProgressMonitor();
- }
- try {
- monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
- try {
- if (rc != null) {
- monitor.subTask(CCorePlugin.getFormattedString("BuildRunnerHelper.removingMarkers", rc.getFullPath().toString())); //$NON-NLS-1$
- rc.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
- }
- } catch (CoreException e) {
- // ignore
- }
- if (project != null) {
- // Remove markers which source is this project from other projects
- try {
- IWorkspace workspace = project.getWorkspace();
- IMarker[] markers = workspace.getRoot().findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE);
- String projectName = project.getName();
- List<IMarker> markersList = new ArrayList<IMarker>();
- for (IMarker marker : markers) {
- if (projectName.equals(marker.getAttribute(IMarker.SOURCE_ID))) {
- markersList.add(marker);
- }
- }
- if (markersList.size() > 0) {
- workspace.deleteMarkers(markersList.toArray(new IMarker[markersList.size()]));
- FixitManager.getInstance().deleteMarkers(markersList.toArray(new IMarker[markersList.size()]));
- }
- } catch (CoreException e) {
- // ignore
- }
- }
-
- } finally {
- monitor.done();
- }
- }
-
- /**
- * Launch build command and process console output.
- *
- * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)}
- * has not been called yet.
- * @throws CoreException
- * @throws IOException
- */
- public int build(IProgressMonitor monitor) throws CoreException, IOException {
- Assert.isNotNull(launcher, "Launch parameters must be set before calling this method"); //$NON-NLS-1$
- Assert.isNotNull(errorParserManager, "Streams must be created and connected before calling this method"); //$NON-NLS-1$
-
- int status = ICommandLauncher.ILLEGAL_COMMAND;
-
- if (monitor == null) {
- monitor = new NullProgressMonitor();
- }
- try {
- monitor.beginTask("", TICKS_EXECUTE_PROGRAM + TICKS_PARSE_OUTPUT); //$NON-NLS-1$
-
- isCancelled = false;
- String pathFromURI = null;
- if (workingDirectoryURI != null) {
- pathFromURI = EFSExtensionManager.getDefault().getPathFromURI(workingDirectoryURI);
- }
- if (pathFromURI == null) {
- // fallback to CWD
- pathFromURI = System.getProperty("user.dir"); //$NON-NLS-1$
- }
- IPath workingDirectory = new Path(pathFromURI);
-
- String errMsg = null;
- monitor.subTask(CCorePlugin.getFormattedString("BuildRunnerHelper.invokingCommand", guessCommandLine(buildCommand.toString(), args))); //$NON-NLS-1$
- Process p = launcher.execute(buildCommand, args, envp, workingDirectory, monitor);
- monitor.worked(TICKS_EXECUTE_PROGRAM);
- if (p != null) {
- try {
- // Close the input of the Process explicitly.
- // We will never write to it.
- p.getOutputStream().close();
- } catch (IOException e) {
- }
-
- status = launcher.waitAndRead(stdout, stderr, monitor);
- monitor.worked(TICKS_PARSE_OUTPUT);
- if (status != ICommandLauncher.OK) {
- errMsg = launcher.getErrorMessage();
- }
- } else {
- errMsg = launcher.getErrorMessage();
- }
-
- if (errMsg != null && !errMsg.isEmpty()) {
- stderr.write(errMsg.getBytes());
- }
-
- isCancelled = monitor.isCanceled();
- if (!isCancelled && project != null) {
- project.setSessionProperty(progressPropertyName, Integer.valueOf(streamProgressMonitor.getWorkDone()));
- }
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- monitor.done();
- }
- return status;
- }
-
- /**
- * Close all streams except console Info stream which is handled by {@link #greeting(String)}/{@link #goodbye()}.
- */
- @Override
- public void close() throws IOException {
- if (!isStreamsOpen)
- return;
-
- try {
- if (stdout != null)
- stdout.close();
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- stdout = null;
- try {
- if (stderr != null)
- stderr.close();
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- stderr = null;
- try {
- if (streamProgressMonitor != null)
- streamProgressMonitor.close();
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- streamProgressMonitor = null;
- try {
- if (consoleOut != null)
- consoleOut.close();
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- consoleOut = null;
- }
- }
- }
- }
- isStreamsOpen = false;
- }
-
- /**
- * Refresh project in the workspace.
- *
- * @param configName - the configuration to refresh
- * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)}
- * has not been called yet.
- */
- public void refreshProject(String configName, IProgressMonitor monitor) {
- if (monitor == null) {
- monitor = new NullProgressMonitor();
- }
- try {
- monitor.beginTask(CCorePlugin.getFormattedString("BuildRunnerHelper.refreshingProject", project.getName()), IProgressMonitor.UNKNOWN); //$NON-NLS-1$
- monitor.subTask(""); //$NON-NLS-1$
-
- // Do not allow the cancel of the refresh, since the builder is external
- // to Eclipse, files may have been created/modified and we will be out-of-sync.
- // The caveat is for huge projects, it may take sometimes at every build.
- // Use the refresh scope manager to refresh
- RefreshScopeManager refreshManager = RefreshScopeManager.getInstance();
- IWorkspaceRunnable runnable = refreshManager.getRefreshRunnable(project, configName);
- ResourcesPlugin.getWorkspace().run(runnable, null, IWorkspace.AVOID_UPDATE, null);
- } catch (CoreException e) {
- // ignore exceptions
- } finally {
- monitor.done();
- }
- }
-
- /**
- * Print a standard greeting to the console.
- * Note that start time of the build is recorded by this method.
- *
- * This method may open an Info stream which must be closed by call to {@link #goodbye()}
- * after all informational messages are printed.
- *
- * @param kind - kind of build. {@link IncrementalProjectBuilder} constants such as
- * {@link IncrementalProjectBuilder#FULL_BUILD} should be used.
- */
- public void greeting(int kind) {
- String msg = CCorePlugin.getFormattedString("BuildRunnerHelper.buildProject", //$NON-NLS-1$
- new String[] { buildKindToString(kind), project.getName() });
- greeting(msg);
- }
-
- /**
- * Print a standard greeting to the console.
- * Note that start time of the build is recorded by this method.
- *
- * This method may open an Info stream which must be closed by call to {@link #goodbye()}
- * after all informational messages are printed.
- *
- * @param kind - kind of build. {@link IncrementalProjectBuilder} constants such as
- * {@link IncrementalProjectBuilder#FULL_BUILD} should be used.
- * @param cfgName - configuration name.
- * @param toolchainName - tool-chain name.
- * @param isSupported - flag indicating if tool-chain is supported on the system.
- */
- public void greeting(int kind, String cfgName, String toolchainName, boolean isSupported) {
- greeting(buildKindToString(kind), cfgName, toolchainName, isSupported);
- }
-
- /**
- * Print a standard greeting to the console.
- * Note that start time of the build is recorded by this method.
- *
- * This method may open an Info stream which must be closed by call to {@link #goodbye()}
- * after all informational messages are printed.
- *
- * @param kind - kind of build as a String.
- * @param cfgName - configuration name.
- * @param toolchainName - tool-chain name.
- * @param isSupported - flag indicating if tool-chain is supported on the system.
- */
- public void greeting(String kind, String cfgName, String toolchainName, boolean isSupported) {
- String msg = CCorePlugin.getFormattedString("BuildRunnerHelper.buildProjectConfiguration", //$NON-NLS-1$
- new String[] { kind, cfgName, project.getName() });
- greeting(msg);
-
- if (!isSupported ){
- String errMsg = CCorePlugin.getFormattedString("BuildRunnerHelper.unsupportedConfiguration", //$NON-NLS-1$
- new String[] { cfgName, toolchainName });
- printLine(errMsg);
- }
- }
-
- /**
- * Print the specified greeting to the console.
- * Note that start time of the build is recorded by this method.
- *
- * This method may open an Info stream which must be closed by call to {@link #goodbye()}
- * after all informational messages are printed.
- */
- public void greeting(String msg) {
- startTime = System.currentTimeMillis();
- if (consoleInfo == null) {
- try {
- consoleInfo = console.getInfoStream();
- } catch (CoreException e) {
- CCorePlugin.log(e);
- }
- }
- toConsole(BuildRunnerHelper.timestamp(startTime) + "**** " + msg + " ****"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- /**
- * Print a standard footer to the console and close Info stream (must be open with one of {@link #greeting(String)} calls).
- * That prints duration of the build determined by start time recorded in {@link #greeting(String)}.
- *
- * <br><strong>Important: {@link #close()} the streams BEFORE calling this method to properly flush all outputs</strong>
- */
- public void goodbye() {
- Assert.isTrue(startTime != 0, "Start time must be set before calling this method."); //$NON-NLS-1$
- Assert.isTrue(consoleInfo != null, "consoleInfo must be open with greetings(...) call before using this method."); //$NON-NLS-1$
-
- endTime = System.currentTimeMillis();
- String duration = durationToString(endTime - startTime);
- String msg = isCancelled ? CCorePlugin.getFormattedString("BuildRunnerHelper.buildCancelled", duration) //$NON-NLS-1$
- : CCorePlugin.getFormattedString("BuildRunnerHelper.buildFinished", duration); //$NON-NLS-1$
- String goodbye = '\n' + timestamp(endTime) + msg + '\n';
-
- try {
- toConsole(goodbye);
- } finally {
- try {
- consoleInfo.close();
- } catch (Exception e) {
- CCorePlugin.log(e);
- } finally {
- consoleInfo = null;
- }
- }
- }
-
- /**
- * Print the given message to the console.
- * @param msg - message to print.
- */
- public void printLine(String msg) {
- Assert.isNotNull(errorParserManager, "Streams must be created and connected before calling this method"); //$NON-NLS-1$
- errorParserManager.processLine(msg);
- }
-
- /**
- * Compose command line that presumably will be run by launcher.
- */
- private static String guessCommandLine(String command, String[] args) {
- StringBuilder buf = new StringBuilder(command + ' ');
- if (args != null) {
- for (String arg : args) {
- buf.append(arg);
- buf.append(' ');
- }
- }
- return buf.toString().trim();
- }
- /**
- * Print a message to the console info output. Note that this message is colored
- * with the color assigned to "Info" stream.
- * @param msg - message to print.
- */
- private void toConsole(String msg) {
- Assert.isNotNull(console, "Streams must be created and connected before calling this method"); //$NON-NLS-1$
- try {
- consoleInfo.write((msg+"\n").getBytes()); //$NON-NLS-1$
- } catch (Exception e) {
- CCorePlugin.log(e);
- }
- }
-
- /**
- * Qualified name to keep previous value of build duration in project session properties.
- */
- private static QualifiedName getProgressPropertyName(IPath buildCommand, String[] args) {
- String name = "buildCommand." + buildCommand.toString(); //$NON-NLS-1$
- if (args != null) {
- for (String arg : args) {
- name = name + ' ' + arg;
- }
- }
- return new QualifiedName(PROGRESS_MONITOR_QUALIFIER, name);
- }
-
- /**
- * Get environment variables from configuration as array of "var=value" suitable
- * for using as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir)
- *
- * @param envMap - map of environment variables
- * @return String array of environment variables in format "var=value"
- */
- public static String[] envMapToEnvp(Map<String, String> envMap) {
- // Convert into envp strings
- List<String> strings = new ArrayList<String>(envMap.size());
- for (Entry<String, String> entry : envMap.entrySet()) {
- strings.add(entry.getKey() + '=' + entry.getValue());
- }
-
- return strings.toArray(new String[strings.size()]);
- }
-
- /**
- * Get environment variables from configuration as array of "var=value" suitable
- * for using as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir)
- *
- * @param cfgDescription - configuration description.
- * @return String array of environment variables in format "var=value". Does not return {@code null}.
- */
- public static String[] getEnvp(ICConfigurationDescription cfgDescription) {
- IEnvironmentVariableManager mngr = CCorePlugin.getDefault().getBuildEnvironmentManager();
- IEnvironmentVariable[] vars = mngr.getVariables(cfgDescription, true);
- // Convert into envp strings
- List<String> strings = new ArrayList<String>(vars.length);
- for (IEnvironmentVariable var : vars) {
- strings.add(var.getName() + '=' + var.getValue());
- }
-
- return strings.toArray(new String[strings.size()]);
- }
-
- /**
- * Convert duration to human friendly format.
- */
- @SuppressWarnings("nls")
- private static String durationToString(long duration) {
- String result = "";
- long days = TimeUnit.MILLISECONDS.toDays(duration);
- if (days > 0) {
- result += days + "d,";
- }
- long hours = TimeUnit.MILLISECONDS.toHours(duration) % 24;
- if (hours > 0) {
- result += hours + "h:";
- }
- long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) % 60;
- if (minutes > 0) {
- result += minutes + "m:";
- }
- long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) % 60;
- if (seconds > 0) {
- result += seconds + "s.";
- }
- long milliseconds = TimeUnit.MILLISECONDS.toMillis(duration) % 1000;
- result += milliseconds + "ms";
-
- return result;
- }
-
- /**
- * Supply timestamp to prefix informational messages.
- */
- @SuppressWarnings("nls")
- private static String timestamp(long time) {
- return new SimpleDateFormat("HH:mm:ss").format(new Date(time)) + " ";
- }
-
- /**
- * Convert build kind to human friendly format.
- */
- private static String buildKindToString(int kind) {
- switch (kind) {
- case IncrementalProjectBuilder.FULL_BUILD:
- return CCorePlugin.getResourceString("BuildRunnerHelper.build"); //$NON-NLS-1$
- case IncrementalProjectBuilder.INCREMENTAL_BUILD:
- return CCorePlugin.getResourceString("BuildRunnerHelper.incrementalBuild"); //$NON-NLS-1$
- case IncrementalProjectBuilder.AUTO_BUILD:
- return CCorePlugin.getResourceString("BuildRunnerHelper.autoBuild"); //$NON-NLS-1$
- case IncrementalProjectBuilder.CLEAN_BUILD:
- return CCorePlugin.getResourceString("BuildRunnerHelper.cleanBuild"); //$NON-NLS-1$
- default:
- return CCorePlugin.getResourceString("BuildRunnerHelper.build"); //$NON-NLS-1$
- }
- }
-}
+/******************************************************************************* + * Copyright (c) 2012, 2016 Andrew Gvozdev 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: + * Andrew Gvozdev - initial API and implementation + * IBM Corporation + *******************************************************************************/ + +package org.eclipse.cdt.internal.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +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.envvar.IEnvironmentVariable; +import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager; +import org.eclipse.cdt.core.model.ICModelMarker; +import org.eclipse.cdt.core.resources.IConsole; +import org.eclipse.cdt.core.resources.RefreshScopeManager; +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; +import org.eclipse.cdt.internal.errorparsers.FixitManager; +import org.eclipse.cdt.utils.EFSExtensionManager; +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.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.QualifiedName; + +/** + * Helper class attempting to unify interactions with build console, + * such as style of console output and handling of console output parsers. + * + * As of CDT 8.1, this class is experimental, internal and work in progress. + * <strong>API is unstable and subject to change.</strong> + */ +public class BuildRunnerHelper implements Closeable { + private static final String PROGRESS_MONITOR_QUALIFIER = CCorePlugin.PLUGIN_ID + ".progressMonitor"; //$NON-NLS-1$ + private static final int PROGRESS_MONITOR_SCALE = 100; + private static final int TICKS_STREAM_PROGRESS_MONITOR = 1 * PROGRESS_MONITOR_SCALE; + private static final int TICKS_EXECUTE_PROGRAM = 1 * PROGRESS_MONITOR_SCALE; + private static final int TICKS_PARSE_OUTPUT = 1 * PROGRESS_MONITOR_SCALE; + + private IProject project; + + private IConsole console = null; + private ErrorParserManager errorParserManager = null; + private StreamProgressMonitor streamProgressMonitor = null; + private OutputStream stdout = null; + private OutputStream stderr = null; + private OutputStream consoleOut = null; + private OutputStream consoleInfo = null; + + private long startTime = 0; + private long endTime = 0; + + private QualifiedName progressPropertyName = null; + + private ICommandLauncher launcher; + private IPath buildCommand; + private String[] args; + private URI workingDirectoryURI; + String[] envp; + + private boolean isStreamsOpen = false; + boolean isCancelled = false; + + /** + * Constructor. + */ + public BuildRunnerHelper(IProject project) { + this.project = project; + } + + /** + * Set parameters for the launch. + * @param envp - String[] array of environment variables in format "var=value" suitable for using + * as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir) + */ + public void setLaunchParameters(ICommandLauncher launcher, IPath buildCommand, String[] args, URI workingDirectoryURI, String[] envp) { + this.launcher = launcher; + launcher.setProject(project); + // Print the command for visual interaction. + launcher.showCommand(true); + + this.buildCommand = buildCommand; + this.args = args; + this.workingDirectoryURI = workingDirectoryURI; + this.envp = envp; + } + + /** + * Open and set up streams for use by {@link BuildRunnerHelper}. + * This must be followed by {@link #close()} to close the streams. Use try...finally for that. + * + * @param epm - ErrorParserManger for error parsing and coloring errors on the console + * @param buildOutputParsers - list of console output parsers or {@code null}. + * @param con - the console. + * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)} + * has not been called yet. + * @throws CoreException + */ + public void prepareStreams(ErrorParserManager epm, List<IConsoleParser> buildOutputParsers, IConsole con, IProgressMonitor monitor) throws CoreException { + errorParserManager = epm; + console = con; + + // Visualize the flow of the streams: + // + // console <- EPM + // ^ + // IConsoleParsers (includes EPM + other parsers) + // ^ + // null <- StreamMomitor <= Sniffer <= Process (!!! the flow starts here!) + // + + isStreamsOpen = true; + + consoleOut = console.getOutputStream(); + // stdout/stderr get to the console through ErrorParserManager + errorParserManager.setOutputStream(consoleOut); + + List<IConsoleParser> parsers = new ArrayList<IConsoleParser>(); + // Using ErrorParserManager as console parser helps to avoid intermixing buffered streams + // as ConsoleOutputSniffer waits for EOL to send a line to console parsers + // separately for each stream. + parsers.add(errorParserManager); + if (buildOutputParsers != null) { + parsers.addAll(buildOutputParsers); + } + + Integer lastWork = null; + if (buildCommand != null && project != null) { + progressPropertyName = getProgressPropertyName(buildCommand, args); + lastWork = (Integer)project.getSessionProperty(progressPropertyName); + } + if (lastWork == null) { + lastWork = TICKS_STREAM_PROGRESS_MONITOR; + } + + streamProgressMonitor = new StreamProgressMonitor(monitor, null, lastWork.intValue()); + ConsoleOutputSniffer sniffer = new ConsoleOutputSniffer(streamProgressMonitor, streamProgressMonitor, parsers.toArray(new IConsoleParser[parsers.size()])); + stdout = sniffer.getOutputStream(); + stderr = sniffer.getErrorStream(); + } + + /** + * @return the output stream to connect stdout of a process + */ + public OutputStream getOutputStream() { + return stdout; + } + + /** + * @return the output stream to connect stderr of a process + */ + public OutputStream getErrorStream() { + return stderr; + } + + /** + * Remove problem markers created for the resource by previous build. + * + * @param rc - resource to remove its markers. + * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)} + * has not been called yet. + * @throws CoreException + */ + public void removeOldMarkers(IResource rc, IProgressMonitor monitor) throws CoreException { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + try { + monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$ + try { + if (rc != null) { + monitor.subTask(CCorePlugin.getFormattedString("BuildRunnerHelper.removingMarkers", rc.getFullPath().toString())); //$NON-NLS-1$ + rc.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); + } + } catch (CoreException e) { + // ignore + } + if (project != null) { + // Remove markers which source is this project from other projects + try { + IWorkspace workspace = project.getWorkspace(); + IMarker[] markers = workspace.getRoot().findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); + String projectName = project.getName(); + List<IMarker> markersList = new ArrayList<IMarker>(); + for (IMarker marker : markers) { + if (projectName.equals(marker.getAttribute(IMarker.SOURCE_ID))) { + markersList.add(marker); + } + } + if (markersList.size() > 0) { + workspace.deleteMarkers(markersList.toArray(new IMarker[markersList.size()])); + FixitManager.getInstance().deleteMarkers(markersList.toArray(new IMarker[markersList.size()])); + } + } catch (CoreException e) { + // ignore + } + } + + } finally { + monitor.done(); + } + } + + /** + * Launch build command and process console output. + * + * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)} + * has not been called yet. + * @throws CoreException + * @throws IOException + */ + public int build(IProgressMonitor monitor) throws CoreException, IOException { + Assert.isNotNull(launcher, "Launch parameters must be set before calling this method"); //$NON-NLS-1$ + Assert.isNotNull(errorParserManager, "Streams must be created and connected before calling this method"); //$NON-NLS-1$ + + int status = ICommandLauncher.ILLEGAL_COMMAND; + + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + try { + monitor.beginTask("", TICKS_EXECUTE_PROGRAM + TICKS_PARSE_OUTPUT); //$NON-NLS-1$ + + isCancelled = false; + String pathFromURI = null; + if (workingDirectoryURI != null) { + pathFromURI = EFSExtensionManager.getDefault().getPathFromURI(workingDirectoryURI); + } + if (pathFromURI == null) { + // fallback to CWD + pathFromURI = System.getProperty("user.dir"); //$NON-NLS-1$ + } + IPath workingDirectory = new Path(pathFromURI); + + String errMsg = null; + monitor.subTask(CCorePlugin.getFormattedString("BuildRunnerHelper.invokingCommand", guessCommandLine(buildCommand.toString(), args))); //$NON-NLS-1$ + Process p = launcher.execute(buildCommand, args, envp, workingDirectory, monitor); + monitor.worked(TICKS_EXECUTE_PROGRAM); + if (p != null) { + try { + // Close the input of the Process explicitly. + // We will never write to it. + p.getOutputStream().close(); + } catch (IOException e) { + } + + status = launcher.waitAndRead(stdout, stderr, monitor); + monitor.worked(TICKS_PARSE_OUTPUT); + if (status != ICommandLauncher.OK) { + errMsg = launcher.getErrorMessage(); + } + } else { + errMsg = launcher.getErrorMessage(); + } + + if (errMsg != null && !errMsg.isEmpty()) { + stderr.write(errMsg.getBytes()); + } + + isCancelled = monitor.isCanceled(); + if (!isCancelled && project != null) { + project.setSessionProperty(progressPropertyName, Integer.valueOf(streamProgressMonitor.getWorkDone())); + } + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + monitor.done(); + } + return status; + } + + /** + * Close all streams except console Info stream which is handled by {@link #greeting(String)}/{@link #goodbye()}. + */ + @Override + public void close() throws IOException { + if (!isStreamsOpen) + return; + + try { + if (stdout != null) + stdout.close(); + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + stdout = null; + try { + if (stderr != null) + stderr.close(); + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + stderr = null; + try { + if (streamProgressMonitor != null) + streamProgressMonitor.close(); + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + streamProgressMonitor = null; + try { + if (consoleOut != null) + consoleOut.close(); + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + consoleOut = null; + } + } + } + } + isStreamsOpen = false; + } + + /** + * Refresh project in the workspace. + * + * @param configName - the configuration to refresh + * @param monitor - progress monitor in the initial state where {@link IProgressMonitor#beginTask(String, int)} + * has not been called yet. + */ + public void refreshProject(String configName, IProgressMonitor monitor) { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + try { + monitor.beginTask(CCorePlugin.getFormattedString("BuildRunnerHelper.refreshingProject", project.getName()), IProgressMonitor.UNKNOWN); //$NON-NLS-1$ + monitor.subTask(""); //$NON-NLS-1$ + + // Do not allow the cancel of the refresh, since the builder is external + // to Eclipse, files may have been created/modified and we will be out-of-sync. + // The caveat is for huge projects, it may take sometimes at every build. + // Use the refresh scope manager to refresh + RefreshScopeManager refreshManager = RefreshScopeManager.getInstance(); + IWorkspaceRunnable runnable = refreshManager.getRefreshRunnable(project, configName); + ResourcesPlugin.getWorkspace().run(runnable, null, IWorkspace.AVOID_UPDATE, null); + } catch (CoreException e) { + // ignore exceptions + } finally { + monitor.done(); + } + } + + /** + * Print a standard greeting to the console. + * Note that start time of the build is recorded by this method. + * + * This method may open an Info stream which must be closed by call to {@link #goodbye()} + * after all informational messages are printed. + * + * @param kind - kind of build. {@link IncrementalProjectBuilder} constants such as + * {@link IncrementalProjectBuilder#FULL_BUILD} should be used. + */ + public void greeting(int kind) { + String msg = CCorePlugin.getFormattedString("BuildRunnerHelper.buildProject", //$NON-NLS-1$ + new String[] { buildKindToString(kind), project.getName() }); + greeting(msg); + } + + /** + * Print a standard greeting to the console. + * Note that start time of the build is recorded by this method. + * + * This method may open an Info stream which must be closed by call to {@link #goodbye()} + * after all informational messages are printed. + * + * @param kind - kind of build. {@link IncrementalProjectBuilder} constants such as + * {@link IncrementalProjectBuilder#FULL_BUILD} should be used. + * @param cfgName - configuration name. + * @param toolchainName - tool-chain name. + * @param isSupported - flag indicating if tool-chain is supported on the system. + */ + public void greeting(int kind, String cfgName, String toolchainName, boolean isSupported) { + greeting(buildKindToString(kind), cfgName, toolchainName, isSupported); + } + + /** + * Print a standard greeting to the console. + * Note that start time of the build is recorded by this method. + * + * This method may open an Info stream which must be closed by call to {@link #goodbye()} + * after all informational messages are printed. + * + * @param kind - kind of build as a String. + * @param cfgName - configuration name. + * @param toolchainName - tool-chain name. + * @param isSupported - flag indicating if tool-chain is supported on the system. + */ + public void greeting(String kind, String cfgName, String toolchainName, boolean isSupported) { + String msg = CCorePlugin.getFormattedString("BuildRunnerHelper.buildProjectConfiguration", //$NON-NLS-1$ + new String[] { kind, cfgName, project.getName() }); + greeting(msg); + + if (!isSupported ){ + String errMsg = CCorePlugin.getFormattedString("BuildRunnerHelper.unsupportedConfiguration", //$NON-NLS-1$ + new String[] { cfgName, toolchainName }); + printLine(errMsg); + } + } + + /** + * Print the specified greeting to the console. + * Note that start time of the build is recorded by this method. + * + * This method may open an Info stream which must be closed by call to {@link #goodbye()} + * after all informational messages are printed. + */ + public void greeting(String msg) { + startTime = System.currentTimeMillis(); + if (consoleInfo == null) { + try { + consoleInfo = console.getInfoStream(); + } catch (CoreException e) { + CCorePlugin.log(e); + } + } + toConsole(BuildRunnerHelper.timestamp(startTime) + "**** " + msg + " ****"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Print a standard footer to the console and close Info stream (must be open with one of {@link #greeting(String)} calls). + * That prints duration of the build determined by start time recorded in {@link #greeting(String)}. + * + * <br><strong>Important: {@link #close()} the streams BEFORE calling this method to properly flush all outputs</strong> + */ + public void goodbye() { + Assert.isTrue(startTime != 0, "Start time must be set before calling this method."); //$NON-NLS-1$ + Assert.isTrue(consoleInfo != null, "consoleInfo must be open with greetings(...) call before using this method."); //$NON-NLS-1$ + + endTime = System.currentTimeMillis(); + String duration = durationToString(endTime - startTime); + String msg = isCancelled ? CCorePlugin.getFormattedString("BuildRunnerHelper.buildCancelled", duration) //$NON-NLS-1$ + : CCorePlugin.getFormattedString("BuildRunnerHelper.buildFinished", duration); //$NON-NLS-1$ + String goodbye = '\n' + timestamp(endTime) + msg + '\n'; + + try { + toConsole(goodbye); + } finally { + try { + consoleInfo.close(); + } catch (Exception e) { + CCorePlugin.log(e); + } finally { + consoleInfo = null; + } + } + } + + /** + * Print the given message to the console. + * @param msg - message to print. + */ + public void printLine(String msg) { + Assert.isNotNull(errorParserManager, "Streams must be created and connected before calling this method"); //$NON-NLS-1$ + errorParserManager.processLine(msg); + } + + /** + * Compose command line that presumably will be run by launcher. + */ + private static String guessCommandLine(String command, String[] args) { + StringBuilder buf = new StringBuilder(command + ' '); + if (args != null) { + for (String arg : args) { + buf.append(arg); + buf.append(' '); + } + } + return buf.toString().trim(); + } + /** + * Print a message to the console info output. Note that this message is colored + * with the color assigned to "Info" stream. + * @param msg - message to print. + */ + private void toConsole(String msg) { + Assert.isNotNull(console, "Streams must be created and connected before calling this method"); //$NON-NLS-1$ + try { + consoleInfo.write((msg+"\n").getBytes()); //$NON-NLS-1$ + } catch (Exception e) { + CCorePlugin.log(e); + } + } + + /** + * Qualified name to keep previous value of build duration in project session properties. + */ + private static QualifiedName getProgressPropertyName(IPath buildCommand, String[] args) { + String name = "buildCommand." + buildCommand.toString(); //$NON-NLS-1$ + if (args != null) { + for (String arg : args) { + name = name + ' ' + arg; + } + } + return new QualifiedName(PROGRESS_MONITOR_QUALIFIER, name); + } + + /** + * Get environment variables from configuration as array of "var=value" suitable + * for using as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir) + * + * @param envMap - map of environment variables + * @return String array of environment variables in format "var=value" + */ + public static String[] envMapToEnvp(Map<String, String> envMap) { + // Convert into envp strings + List<String> strings = new ArrayList<String>(envMap.size()); + for (Entry<String, String> entry : envMap.entrySet()) { + strings.add(entry.getKey() + '=' + entry.getValue()); + } + + return strings.toArray(new String[strings.size()]); + } + + /** + * Get environment variables from configuration as array of "var=value" suitable + * for using as "envp" with Runtime.exec(String[] cmdarray, String[] envp, File dir) + * + * @param cfgDescription - configuration description. + * @return String array of environment variables in format "var=value". Does not return {@code null}. + */ + public static String[] getEnvp(ICConfigurationDescription cfgDescription) { + IEnvironmentVariableManager mngr = CCorePlugin.getDefault().getBuildEnvironmentManager(); + IEnvironmentVariable[] vars = mngr.getVariables(cfgDescription, true); + // Convert into envp strings + List<String> strings = new ArrayList<String>(vars.length); + for (IEnvironmentVariable var : vars) { + strings.add(var.getName() + '=' + var.getValue()); + } + + return strings.toArray(new String[strings.size()]); + } + + /** + * Convert duration to human friendly format. + */ + @SuppressWarnings("nls") + private static String durationToString(long duration) { + String result = ""; + long days = TimeUnit.MILLISECONDS.toDays(duration); + if (days > 0) { + result += days + "d,"; + } + long hours = TimeUnit.MILLISECONDS.toHours(duration) % 24; + if (hours > 0) { + result += hours + "h:"; + } + long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) % 60; + if (minutes > 0) { + result += minutes + "m:"; + } + long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) % 60; + if (seconds > 0) { + result += seconds + "s."; + } + long milliseconds = TimeUnit.MILLISECONDS.toMillis(duration) % 1000; + result += milliseconds + "ms"; + + return result; + } + + /** + * Supply timestamp to prefix informational messages. + */ + @SuppressWarnings("nls") + private static String timestamp(long time) { + return new SimpleDateFormat("HH:mm:ss").format(new Date(time)) + " "; + } + + /** + * Convert build kind to human friendly format. + */ + private static String buildKindToString(int kind) { + switch (kind) { + case IncrementalProjectBuilder.FULL_BUILD: + return CCorePlugin.getResourceString("BuildRunnerHelper.build"); //$NON-NLS-1$ + case IncrementalProjectBuilder.INCREMENTAL_BUILD: + return CCorePlugin.getResourceString("BuildRunnerHelper.incrementalBuild"); //$NON-NLS-1$ + case IncrementalProjectBuilder.AUTO_BUILD: + return CCorePlugin.getResourceString("BuildRunnerHelper.autoBuild"); //$NON-NLS-1$ + case IncrementalProjectBuilder.CLEAN_BUILD: + return CCorePlugin.getResourceString("BuildRunnerHelper.cleanBuild"); //$NON-NLS-1$ + default: + return CCorePlugin.getResourceString("BuildRunnerHelper.build"); //$NON-NLS-1$ + } + } +} |