[eclipse-yocto] [PATCH 1/4] Add extended command launcher facility for poky plugin

Tim Orling timothy.t.orling at linux.intel.com
Thu Feb 15 21:50:42 PST 2018


From: Chin Huat Ang <chin.huat.ang at intel.com>

Signed-off-by: Chin Huat Ang <chin.huat.ang at intel.com>
---
 plugins/org.yocto.docker.launcher/.classpath       |    7 +
 plugins/org.yocto.docker.launcher/.project         |   28 +
 .../.settings/org.eclipse.jdt.core.prefs           |    7 +
 .../org.yocto.docker.launcher/META-INF/MANIFEST.MF |   21 +
 plugins/org.yocto.docker.launcher/build.properties |    5 +
 plugins/org.yocto.docker.launcher/plugin.xml       |   14 +
 .../src/org/yocto/docker/launcher/Activator.java   |   41 +
 .../docker/launcher/ContainerCommandLauncher.java  |  477 +++++++
 .../launcher/ContainerCommandLauncherFactory.java  |  277 ++++
 .../launcher/PokyContainerCommandLauncher.java     |   26 +
 .../PokyContainerCommandLauncherFactory.java       |   60 +
 .../docker/launcher/ui/ContainerLauncher.java      | 1467 ++++++++++++++++++++
 .../docker/launcher/ui/PokyContainerLauncher.java  |   25 +
 13 files changed, 2455 insertions(+)
 create mode 100644 plugins/org.yocto.docker.launcher/.classpath
 create mode 100644 plugins/org.yocto.docker.launcher/.project
 create mode 100644 plugins/org.yocto.docker.launcher/.settings/org.eclipse.jdt.core.prefs
 create mode 100644 plugins/org.yocto.docker.launcher/META-INF/MANIFEST.MF
 create mode 100644 plugins/org.yocto.docker.launcher/build.properties
 create mode 100644 plugins/org.yocto.docker.launcher/plugin.xml
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/Activator.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncher.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncherFactory.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncher.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncherFactory.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/ContainerLauncher.java
 create mode 100644 plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/PokyContainerLauncher.java

diff --git a/plugins/org.yocto.docker.launcher/.classpath b/plugins/org.yocto.docker.launcher/.classpath
new file mode 100644
index 00000000000..eca7bdba8f0
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.yocto.docker.launcher/.project b/plugins/org.yocto.docker.launcher/.project
new file mode 100644
index 00000000000..abb53a76a1f
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.yocto.docker.launcher</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.yocto.docker.launcher/.settings/org.eclipse.jdt.core.prefs b/plugins/org.yocto.docker.launcher/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..0c68a61dca8
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/plugins/org.yocto.docker.launcher/META-INF/MANIFEST.MF b/plugins/org.yocto.docker.launcher/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..3e29b1f81e7
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/META-INF/MANIFEST.MF
@@ -0,0 +1,21 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Launcher
+Bundle-SymbolicName: org.yocto.docker.launcher;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.yocto.docker.launcher.Activator
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.cdt.core;bundle-version="6.4.0",
+ org.eclipse.cdt.docker.launcher;bundle-version="1.1.0",
+ org.eclipse.core.resources;bundle-version="3.12.0",
+ org.eclipse.linuxtools.docker.ui;bundle-version="3.2.0",
+ org.eclipse.cdt.managedbuilder.core;bundle-version="8.6.0",
+ org.eclipse.core.variables;bundle-version="3.4.0",
+ org.eclipse.ui.workbench;bundle-version="3.110.1",
+ org.yocto.sdk.ide;bundle-version="1.4.1",
+ org.apache.commons.compress;bundle-version="1.6.0",
+ org.eclipse.jface;bundle-version="3.13.1",
+ org.eclipse.linuxtools.docker.core;bundle-version="3.2.0",
+ org.eclipse.ui.console;bundle-version="3.7.1"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
diff --git a/plugins/org.yocto.docker.launcher/build.properties b/plugins/org.yocto.docker.launcher/build.properties
new file mode 100644
index 00000000000..e9863e281ea
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml
diff --git a/plugins/org.yocto.docker.launcher/plugin.xml b/plugins/org.yocto.docker.launcher/plugin.xml
new file mode 100644
index 00000000000..74bba331c6e
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/plugin.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+       id="CommandLauncherFactories"
+       name="%ContainerCommandLauncherFactory.name"
+       point="org.eclipse.cdt.core.CommandLauncherFactory">
+       <factory
+          id="ContainerCommandLauncherFactory"
+          class="org.yocto.docker.launcher.PokyContainerCommandLauncherFactory"
+          priority="10">
+      </factory>
+   </extension>
+</plugin>
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/Activator.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/Activator.java
new file mode 100644
index 00000000000..6666537e130
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/Activator.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Intel Corporation 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:
+ *    Intel Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.yocto.docker.launcher;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+	private static BundleContext context;
+
+	static BundleContext getContext() {
+		return context;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext bundleContext) throws Exception {
+		Activator.context = bundleContext;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext bundleContext) throws Exception {
+		Activator.context = null;
+	}
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncher.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncher.java
new file mode 100644
index 00000000000..1f19475f7b0
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncher.java
@@ -0,0 +1,477 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Intel Corporation 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:
+ *    Intel Corporation - initial API and implementation
+ *******************************************************************************/
+package org.yocto.docker.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.eclipse.cdt.core.ICommandLauncher;
+import org.eclipse.cdt.core.model.CoreModel;
+import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
+import org.eclipse.cdt.docker.launcher.DockerLaunchUIPlugin;
+import org.eclipse.cdt.internal.core.ProcessClosure;
+import org.eclipse.cdt.internal.docker.launcher.Messages;
+import org.eclipse.cdt.internal.docker.launcher.PreferenceConstants;
+import org.eclipse.cdt.managedbuilder.buildproperties.IOptionalBuildProperties;
+import org.eclipse.cdt.managedbuilder.core.IConfiguration;
+import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.core.variables.VariablesPlugin;
+import org.eclipse.linuxtools.docker.ui.launch.IErrorMessageHolder;
+import org.eclipse.linuxtools.internal.docker.ui.launch.ContainerCommandProcess;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.service.prefs.Preferences;
+import org.yocto.docker.launcher.ui.PokyContainerLauncher;
+
+/**
+ *
+ * @see org.eclipse.cdt.docker.launcher.ContainerCommandLauncher
+ *
+ */
+ at SuppressWarnings("restriction")
+public class ContainerCommandLauncher
+		implements ICommandLauncher, IErrorMessageHolder {
+
+	public final static String CONTAINER_BUILD_ENABLED = DockerLaunchUIPlugin.PLUGIN_ID
+			+ ".containerbuild.property.enablement"; //$NON-NLS-1$
+	public final static String CONNECTION_ID = DockerLaunchUIPlugin.PLUGIN_ID
+			+ ".containerbuild.property.connection"; //$NON-NLS-1$
+	public final static String IMAGE_ID = DockerLaunchUIPlugin.PLUGIN_ID
+			+ ".containerbuild.property.image"; //$NON-NLS-1$
+	public final static String VOLUMES_ID = DockerLaunchUIPlugin.PLUGIN_ID
+			+ ".containerbuild.property.volumes"; //$NON-NLS-1$
+	public final static String SELECTED_VOLUMES_ID = DockerLaunchUIPlugin.PLUGIN_ID
+			+ ".containerbuild.property.selectedvolumes"; //$NON-NLS-1$
+
+	public final static String VOLUME_SEPARATOR_REGEX = "[|]"; //$NON-NLS-1$
+
+	private IProject fProject;
+	private Process fProcess;
+	private boolean fShowCommand;
+	private String fErrorMessage;
+	private Properties fEnvironment;
+
+	private String[] commandArgs;
+	private String fImageName = ""; //$NON-NLS-1$
+
+	public final static int COMMAND_CANCELED = ICommandLauncher.COMMAND_CANCELED;
+	public final static int ILLEGAL_COMMAND = ICommandLauncher.ILLEGAL_COMMAND;
+	public final static int OK = ICommandLauncher.OK;
+
+	private static final String NEWLINE = System.getProperty("line.separator", //$NON-NLS-1$
+			"\n"); //$NON-NLS-1$
+
+	private static Map<IProject, Integer> uidMap = new HashMap<>();
+
+	/**
+	 * The number of milliseconds to pause between polling.
+	 */
+	protected static final long DELAY = 50L;
+
+	@Override
+	public void setProject(IProject project) {
+		this.fProject = project;
+	}
+
+	@Override
+	public IProject getProject() {
+		return fProject;
+	}
+
+	@SuppressWarnings("unused")
+	private String getImageName() {
+		return fImageName;
+	}
+
+	private void setImageName(String imageName) {
+		fImageName = imageName;
+	}
+
+	@Override
+	public void showCommand(boolean show) {
+		this.fShowCommand = show;
+	}
+
+	@Override
+	public String getErrorMessage() {
+		return fErrorMessage;
+	}
+
+	@Override
+	public void setErrorMessage(String error) {
+		fErrorMessage = error;
+	}
+
+	@Override
+	public String[] getCommandArgs() {
+		return commandArgs;
+	}
+
+	@Override
+	public Properties getEnvironment() {
+		return fEnvironment;
+	}
+
+	@Override
+	public String getCommandLine() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	protected Integer getUid() {
+
+		String os = System.getProperty("os.name"); //$NON-NLS-1$
+		if (os.indexOf("nux") > 0) { //$NON-NLS-1$
+			// first try and see if we have already run a command on this
+			// project
+			Integer cachedUid = uidMap.get(fProject);
+			if (cachedUid != null) {
+				return cachedUid;
+			}
+
+			try {
+				Integer resolvedUid = (Integer) Files.getAttribute(
+						fProject.getLocation().toFile().toPath(),
+						"unix:uid"); //$NON-NLS-1$
+				// store the uid for possible later usage
+				uidMap.put(fProject, resolvedUid);
+				return resolvedUid;
+			} catch (IOException e) {
+				// do nothing...leave as null
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public Process execute(IPath commandPath, String[] args, String[] env,
+			IPath workingDirectory, IProgressMonitor monitor)
+			throws CoreException {
+
+		HashMap<String, String> labels = new HashMap<>();
+		labels.put("org.eclipse.cdt.container-command", ""); //$NON-NLS-1$ //$NON-NLS-2$
+		String projectName = fProject.getName();
+		labels.put("org.eclipse.cdt.project-name", projectName); //$NON-NLS-1$
+
+		List<String> additionalDirs = new ArrayList<>();
+
+		//
+		IPath projectLocation = fProject.getLocation();
+		String projectPath = projectLocation.toPortableString();
+		if (projectLocation.getDevice() != null) {
+			projectPath = "/" + projectPath.replace(':', '/'); //$NON-NLS-1$
+		}
+		additionalDirs.add(projectPath);
+
+		ArrayList<String> commandSegments = new ArrayList<>();
+
+		StringBuilder b = new StringBuilder();
+		String commandString = commandPath.toPortableString();
+		if (commandPath.getDevice() != null) {
+			commandString = "/" + commandString.replace(':', '/'); //$NON-NLS-1$
+		}
+		b.append(commandString);
+		commandSegments.add(commandString);
+		for (String arg : args) {
+			b.append(" "); //$NON-NLS-1$
+			String realArg = VariablesPlugin.getDefault()
+					.getStringVariableManager().performStringSubstitution(arg);
+			if (Platform.getOS().equals(Platform.OS_WIN32)) {
+				// check if file exists and if so, add an additional directory
+				IPath p = new Path(realArg);
+				if (p.isValidPath(realArg) && p.getDevice() != null) {
+					File f = p.toFile();
+					String modifiedArg = realArg;
+					// if the directory of the arg as a file exists, we mount it
+					// and modify the argument to be unix-style
+					if (f.isFile()) {
+						f = f.getParentFile();
+						modifiedArg = "/" //$NON-NLS-1$
+								+ p.toPortableString().replace(':', '/');
+						p = p.removeLastSegments(1);
+					}
+					if (f != null && f.exists()) {
+						additionalDirs.add(
+								"/" + p.toPortableString().replace(':', '/')); //$NON-NLS-1$
+						realArg = modifiedArg;
+					}
+				}
+			} else if (realArg.startsWith("/")) { //$NON-NLS-1$
+				// check if file directory exists and if so, add an additional
+				// directory
+				IPath p = new Path(realArg);
+				if (p.isValidPath(realArg)) {
+					File f = p.toFile();
+					if (f.isFile()) {
+						f = f.getParentFile();
+					}
+					if (f != null && f.exists()) {
+						additionalDirs.add(f.getAbsolutePath());
+					}
+				}
+			}
+			b.append(realArg);
+			commandSegments.add(realArg);
+		}
+
+		commandArgs = commandSegments.toArray(new String[0]);
+
+		String commandDir = commandPath.removeLastSegments(1).toString();
+		if (commandDir.isEmpty()) {
+			commandDir = null;
+		} else if (commandPath.getDevice() != null) {
+			commandDir = "/" + commandDir.replace(':', '/'); //$NON-NLS-1$
+		}
+
+		IProject[] referencedProjects = fProject.getReferencedProjects();
+		for (IProject referencedProject : referencedProjects) {
+			String referencedProjectPath = referencedProject.getLocation()
+					.toPortableString();
+			if (referencedProject.getLocation().getDevice() != null) {
+				referencedProjectPath = "/" //$NON-NLS-1$
+						+ referencedProjectPath.replace(':', '/');
+			}
+			additionalDirs
+					.add(referencedProjectPath);
+		}
+
+		String command = b.toString();
+
+		String workingDir = workingDirectory.makeAbsolute().toPortableString();
+		if (workingDirectory.toPortableString().equals(".")) { //$NON-NLS-1$
+			workingDir = "/tmp"; //$NON-NLS-1$
+		} else if (workingDirectory.getDevice() != null) {
+			workingDir = "/" + workingDir.replace(':', '/'); //$NON-NLS-1$
+		}
+		parseEnvironment(env);
+		Map<String, String> origEnv = null;
+
+		boolean supportStdin = false;
+
+		boolean privilegedMode = false;
+
+		// TODO: make this into an API
+		PokyContainerLauncher launcher = new PokyContainerLauncher();
+
+		Preferences prefs = InstanceScope.INSTANCE
+				.getNode(DockerLaunchUIPlugin.PLUGIN_ID);
+
+		boolean keepContainer = prefs.getBoolean(
+				PreferenceConstants.KEEP_CONTAINER_AFTER_LAUNCH, false);
+
+		ICConfigurationDescription cfgd = CoreModel.getDefault()
+				.getProjectDescription(fProject).getActiveConfiguration();
+		IConfiguration cfg = ManagedBuildManager
+				.getConfigurationForDescription(cfgd);
+		if (cfg == null) {
+			return null;
+		}
+		IOptionalBuildProperties props = cfg.getOptionalBuildProperties();
+
+		// Add any specified volumes to additional dir list
+		String selectedVolumeString = props.getProperty(SELECTED_VOLUMES_ID);
+		if (selectedVolumeString != null && !selectedVolumeString.isEmpty()) {
+			String[] selectedVolumes = selectedVolumeString
+					.split(VOLUME_SEPARATOR_REGEX);
+			if (Platform.getOS().equals(Platform.OS_WIN32)) {
+				for (String selectedVolume : selectedVolumes) {
+					IPath path = new Path(selectedVolume);
+					String selectedPath = path.toPortableString();
+					if (path.getDevice() != null) {
+						selectedPath = "/" + selectedPath.replace(':', '/'); //$NON-NLS-1$
+					}
+					additionalDirs.add(selectedPath);
+				}
+			} else {
+				additionalDirs.addAll(Arrays.asList(selectedVolumes));
+			}
+		}
+
+		String connectionName = props
+				.getProperty(ContainerCommandLauncher.CONNECTION_ID);
+		if (connectionName == null) {
+			return null;
+		}
+		String imageName = props
+				.getProperty(ContainerCommandLauncher.IMAGE_ID);
+		if (imageName == null) {
+			return null;
+		}
+		setImageName(imageName);
+
+		Integer uid = getUid();
+
+		fProcess = launcher.runCommand(connectionName, imageName, fProject,
+				this,
+				command,
+				commandDir,
+				workingDir,
+				additionalDirs,
+				origEnv, fEnvironment, supportStdin, privilegedMode,
+				labels, keepContainer, uid);
+
+		return fProcess;
+	}
+
+	/**
+	 * Parse array of "ENV=value" pairs to Properties.
+	 */
+	private void parseEnvironment(String[] env) {
+		fEnvironment = null;
+		if (env != null) {
+			fEnvironment = new Properties();
+			for (String envStr : env) {
+				// Split "ENV=value" and put in Properties
+				int pos = envStr.indexOf('='); // $NON-NLS-1$
+				if (pos < 0)
+					pos = envStr.length();
+				String key = envStr.substring(0, pos);
+				String value = envStr.substring(pos + 1);
+				fEnvironment.put(key, value);
+			}
+		}
+	}
+
+	@Override
+	public int waitAndRead(OutputStream out, OutputStream err) {
+		printImageHeader(out);
+
+		if (fShowCommand) {
+			printCommandLine(out);
+		}
+
+		if (fProcess == null) {
+			return ILLEGAL_COMMAND;
+		}
+		ProcessClosure closure = new ProcessClosure(fProcess, out, err);
+		closure.runBlocking(); // a blocking call
+		return OK;
+	}
+
+	@Override
+	public int waitAndRead(OutputStream output, OutputStream err,
+			IProgressMonitor monitor) {
+		printImageHeader(output);
+
+		if (fShowCommand) {
+			printCommandLine(output);
+		}
+
+		if (fProcess == null) {
+			return ILLEGAL_COMMAND;
+		}
+
+		ProcessClosure closure = new ProcessClosure(fProcess, output, err);
+		closure.runNonBlocking();
+		Runnable watchProcess = () -> {
+			try {
+				fProcess.waitFor();
+			} catch (InterruptedException e) {
+				// ignore
+			}
+			closure.terminate();
+		};
+		Thread t = new Thread(watchProcess);
+		t.start();
+		while (!monitor.isCanceled() && closure.isAlive()) {
+			try {
+				Thread.sleep(DELAY);
+			} catch (InterruptedException ie) {
+				break;
+			}
+		}
+		try {
+			t.join(500);
+		} catch (InterruptedException e1) {
+			// ignore
+		}
+		int state = OK;
+
+		// Operation canceled by the user, terminate abnormally.
+		if (monitor.isCanceled()) {
+			closure.terminate();
+			state = COMMAND_CANCELED;
+			setErrorMessage(Messages.CommandLauncher_CommandCancelled);
+		}
+		try {
+			fProcess.waitFor();
+		} catch (InterruptedException e) {
+			// ignore
+		}
+
+		monitor.done();
+		return state;
+	}
+
+	protected void printImageHeader(OutputStream os) {
+		if (os != null) {
+			try {
+				os.write(NLS
+						.bind(Messages.ContainerCommandLauncher_image_msg,
+								((ContainerCommandProcess) fProcess).getImage())
+						.getBytes());
+				os.write(NEWLINE.getBytes());
+				os.flush();
+			} catch (IOException e) {
+				// ignore
+			}
+		}
+	}
+
+	protected void printCommandLine(OutputStream os) {
+		if (os != null) {
+			try {
+				os.write(getCommandLineQuoted(getCommandArgs(), true)
+						.getBytes());
+				os.flush();
+			} catch (IOException e) {
+				// ignore;
+			}
+		}
+	}
+
+	private String getCommandLineQuoted(String[] commandArgs, boolean quote) {
+		StringBuilder buf = new StringBuilder();
+		if (commandArgs != null) {
+			for (String commandArg : commandArgs) {
+				if (quote && (commandArg.contains(" ")
+						|| commandArg.contains("\"")
+						|| commandArg.contains("\\"))) {
+					commandArg = '"' + commandArg.replaceAll("\\\\", "\\\\\\\\")
+							.replaceAll("\"", "\\\\\"") + '"';
+				}
+				buf.append(commandArg);
+				buf.append(' ');
+			}
+			buf.append(NEWLINE);
+		}
+		return buf.toString();
+	}
+
+	protected String getCommandLine(String[] commandArgs) {
+		return getCommandLineQuoted(commandArgs, false);
+	}
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncherFactory.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncherFactory.java
new file mode 100644
index 00000000000..f1ec5cd1f20
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ContainerCommandLauncherFactory.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Red Hat Inc., Intel Corporation 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:
+ *     Red Hat Inc. - initial API and implementation
+ *     Intel Corporation - add support for Yocto Project CROPS images
+ *******************************************************************************/
+package org.yocto.docker.launcher;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.cdt.core.ICommandLauncher;
+import org.eclipse.cdt.core.ICommandLauncherFactory;
+import org.eclipse.cdt.core.model.CoreModel;
+import org.eclipse.cdt.core.settings.model.CIncludePathEntry;
+import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
+import org.eclipse.cdt.core.settings.model.ICIncludePathEntry;
+import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
+import org.eclipse.cdt.docker.launcher.ContainerCommandLauncher;
+import org.eclipse.cdt.docker.launcher.DockerLaunchUIPlugin;
+import org.eclipse.cdt.internal.docker.launcher.Messages;
+import org.eclipse.cdt.managedbuilder.buildproperties.IOptionalBuildProperties;
+import org.eclipse.cdt.managedbuilder.core.IConfiguration;
+import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.linuxtools.docker.ui.launch.ContainerLauncher;
+
+/**
+ *
+ * @see org.eclipse.cdt.docker.launcher.ContainerCommandLauncherFactory
+ *
+ */
+ at SuppressWarnings("restriction")
+public class ContainerCommandLauncherFactory
+		implements ICommandLauncherFactory {
+
+	protected ICommandLauncher createContainerCommandLauncher() {
+		return new ContainerCommandLauncher();
+	}
+
+	@Override
+	public ICommandLauncher getCommandLauncher(IProject project) {
+		// check if container build enablement has been checked
+		ICConfigurationDescription cfgd = CoreModel.getDefault()
+				.getProjectDescription(project)
+					.getActiveConfiguration();
+		IConfiguration cfg = ManagedBuildManager
+				.getConfigurationForDescription(cfgd);
+		// TODO: figure out why this occurs
+		if (cfg == null) {
+			return null;
+		}
+		IOptionalBuildProperties props = cfg.getOptionalBuildProperties();
+		if (props != null) {
+			String enablementProperty = props.getProperty(
+					ContainerCommandLauncher.CONTAINER_BUILD_ENABLED);
+			if (enablementProperty != null) {
+				boolean enableContainer = Boolean
+						.parseBoolean(enablementProperty);
+				// enablement has occurred, we can return a
+				// ContainerCommandLauncher
+				if (enableContainer) {
+					return createContainerCommandLauncher();
+				}
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public ICommandLauncher getCommandLauncher(
+			ICConfigurationDescription cfgd) {
+		// check if container build enablement has been checked
+		IConfiguration cfg = ManagedBuildManager
+				.getConfigurationForDescription(cfgd);
+		// TODO: figure out why this occurs
+		if (cfg == null) {
+			return null;
+		}
+		IOptionalBuildProperties props = cfg.getOptionalBuildProperties();
+		if (props != null) {
+			String enablementProperty = props.getProperty(
+					ContainerCommandLauncher.CONTAINER_BUILD_ENABLED);
+			if (enablementProperty != null) {
+				boolean enableContainer = Boolean
+						.parseBoolean(enablementProperty);
+				// enablement has occurred, we can return a
+				// ContainerCommandLauncher
+				if (enableContainer) {
+					return createContainerCommandLauncher();
+				}
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public void registerLanguageSettingEntries(IProject project,
+			List<? extends ICLanguageSettingEntry> langEntries) {
+		@SuppressWarnings("unchecked")
+		List<ICLanguageSettingEntry> entries = (List<ICLanguageSettingEntry>) langEntries;
+		ICConfigurationDescription cfgd = CoreModel.getDefault()
+				.getProjectDescription(project).getActiveConfiguration();
+		IConfiguration cfg = ManagedBuildManager
+				.getConfigurationForDescription(cfgd);
+		IOptionalBuildProperties props = cfg.getOptionalBuildProperties();
+		if (props != null) {
+			String enablementProperty = props.getProperty(
+					ContainerCommandLauncher.CONTAINER_BUILD_ENABLED);
+			if (enablementProperty != null) {
+				boolean enableContainer = Boolean
+						.parseBoolean(enablementProperty);
+				if (enableContainer) {
+					String connectionName = props.getProperty(
+							ContainerCommandLauncher.CONNECTION_ID);
+					String imageName = props
+							.getProperty(ContainerCommandLauncher.IMAGE_ID);
+					if (connectionName == null || connectionName.isEmpty()
+							|| imageName == null || imageName.isEmpty()) {
+						DockerLaunchUIPlugin.logErrorMessage(
+								Messages.ContainerCommandLauncher_invalid_values);
+						return;
+					}
+					ContainerLauncher launcher = new ContainerLauncher();
+					List<String> paths = new ArrayList<>();
+					for (ICLanguageSettingEntry entry : entries) {
+						if (entry instanceof ICIncludePathEntry) {
+							paths.add(entry.getValue());
+						}
+					}
+					if (paths.size() > 0) {
+						// Create a directory to put the header files for
+						// the image. Use the connection name to form
+						// the directory name as the connection may be
+						// connected to a different repo using the same
+						// image name.
+						IPath pluginPath = Platform.getStateLocation(Platform
+								.getBundle(DockerLaunchUIPlugin.PLUGIN_ID))
+								.append("HEADERS"); //$NON-NLS-1$
+						pluginPath.toFile().mkdir();
+						pluginPath = pluginPath
+								.append(getCleanName(connectionName));
+						pluginPath.toFile().mkdir();
+						// To allow the user to later manage the headers, store
+						// the
+						// real connection name in a file.
+						IPath connectionNamePath = pluginPath.append(".name"); //$NON-NLS-1$
+						File f = connectionNamePath.toFile();
+						try {
+							f.createNewFile();
+							try (FileWriter writer = new FileWriter(f);
+									BufferedWriter bufferedWriter = new BufferedWriter(
+											writer);) {
+								bufferedWriter.write(connectionName);
+								bufferedWriter.newLine();
+							} catch (IOException e) {
+								DockerLaunchUIPlugin.log(e);
+								return;
+							}
+							pluginPath = pluginPath
+									.append(getCleanName(imageName));
+							pluginPath.toFile().mkdir();
+							// To allow the user to later manage the headers,
+							// store the
+							// real image name in a file.
+							IPath imageNamePath = pluginPath.append(".name"); //$NON-NLS-1$
+							f = imageNamePath.toFile();
+							f.createNewFile();
+							try (FileWriter writer = new FileWriter(f);
+									BufferedWriter bufferedWriter = new BufferedWriter(
+											writer);) {
+								bufferedWriter.write(imageName);
+								bufferedWriter.newLine();
+							} catch (IOException e) {
+								DockerLaunchUIPlugin.log(e);
+								return;
+							}
+						} catch (IOException e) {
+							DockerLaunchUIPlugin.log(e);
+							return;
+						}
+						IPath hostDir = pluginPath;
+						@SuppressWarnings("unused")
+						int status = launcher.fetchContainerDirs(connectionName,
+								imageName,
+								paths, hostDir);
+					}
+				}
+			}
+		}
+
+	}
+
+	@Override
+	public List<ICLanguageSettingEntry> verifyLanguageSettingEntries(
+			IProject project, List<ICLanguageSettingEntry> entries) {
+		if (entries == null) {
+			return null;
+		}
+		ICConfigurationDescription cfgd = CoreModel.getDefault()
+				.getProjectDescription(project).getActiveConfiguration();
+		IConfiguration cfg = ManagedBuildManager
+				.getConfigurationForDescription(cfgd);
+		IOptionalBuildProperties props = cfg.getOptionalBuildProperties();
+		if (props != null) {
+			String enablementProperty = props.getProperty(
+					ContainerCommandLauncher.CONTAINER_BUILD_ENABLED);
+			if (enablementProperty != null) {
+				boolean enableContainer = Boolean
+						.parseBoolean(enablementProperty);
+				if (enableContainer) {
+					String connectionName = props.getProperty(
+							ContainerCommandLauncher.CONNECTION_ID);
+					String imageName = props
+							.getProperty(ContainerCommandLauncher.IMAGE_ID);
+					if (connectionName == null || connectionName.isEmpty()
+							|| imageName == null || imageName.isEmpty()) {
+						DockerLaunchUIPlugin.logErrorMessage(
+								Messages.ContainerCommandLauncher_invalid_values);
+						return entries;
+					}
+
+					ContainerLauncher launcher = new ContainerLauncher();
+					Set<String> copiedVolumes = launcher
+							.getCopiedVolumes(connectionName, imageName);
+					List<ICLanguageSettingEntry> newEntries = new ArrayList<>();
+					IPath pluginPath = Platform.getStateLocation(
+							Platform.getBundle(DockerLaunchUIPlugin.PLUGIN_ID));
+					IPath hostDir = pluginPath.append("HEADERS") //$NON-NLS-1$
+							.append(getCleanName(connectionName))
+							.append(getCleanName(imageName));
+
+					for (ICLanguageSettingEntry entry : entries) {
+						if (entry instanceof ICIncludePathEntry) {
+							if (copiedVolumes
+									.contains(((ICIncludePathEntry) entry)
+											.getName().toString())) {
+																	// //$NON-NLS-2$
+								IPath newPath = hostDir.append(entry.getName());
+								CIncludePathEntry newEntry = new CIncludePathEntry(
+										newPath.toString(),
+										entry.getFlags());
+								newEntries.add(newEntry);
+								continue;
+							} else {
+								newEntries.add(entry);
+							}
+						} else {
+							newEntries.add(entry);
+						}
+					}
+					return newEntries;
+				}
+			}
+		}
+		return entries;
+	}
+
+	private String getCleanName(String name) {
+		String cleanName = name.replace("unix:///", "unix_"); //$NON-NLS-1$ //$NON-NLS-2$
+		cleanName = cleanName.replace("tcp://", "tcp_"); //$NON-NLS-1$ //$NON-NLS-2$
+		return cleanName.replaceAll("[:/.]", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncher.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncher.java
new file mode 100644
index 00000000000..b6039d00ce9
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncher.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Intel Corporation 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:
+ *    Intel Corporation - initial API and implementation
+ *******************************************************************************/
+package org.yocto.docker.launcher;
+
+import org.eclipse.cdt.core.ICommandLauncher;
+import org.eclipse.linuxtools.docker.ui.launch.IErrorMessageHolder;
+
+public class PokyContainerCommandLauncher
+		extends ContainerCommandLauncher
+		implements ICommandLauncher, IErrorMessageHolder {
+
+	@Override
+	protected Integer getUid() {
+		return null;
+	}
+
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncherFactory.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncherFactory.java
new file mode 100644
index 00000000000..3d492d4f177
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/PokyContainerCommandLauncherFactory.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Intel Corporation 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:
+ *    Intel Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.yocto.docker.launcher;
+
+import org.eclipse.cdt.core.ICommandLauncher;
+import org.eclipse.cdt.core.ICommandLauncherFactory;
+import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.yocto.sdk.ide.natures.YoctoSDKProjectNature;
+
+public class PokyContainerCommandLauncherFactory
+		extends ContainerCommandLauncherFactory
+		implements ICommandLauncherFactory {
+
+	protected ICommandLauncher createContainerCommandLauncher() {
+		return new PokyContainerCommandLauncher();
+	}
+
+	@Override
+	public ICommandLauncher getCommandLauncher(IProject project) {
+
+		// only return extended container command launcher for projects with Yocto SDK nature
+		try {
+			if (project.getNature(YoctoSDKProjectNature.YoctoSDK_NATURE_ID) == null)
+				return null;
+		} catch (CoreException e) {
+			// Do nothing if project does not exist, or isn't open, or doesn't contain the Yocto SDK nature.
+			return null;
+		}
+
+		return super.getCommandLauncher(project);
+	}
+
+	@Override
+	public ICommandLauncher getCommandLauncher(
+			ICConfigurationDescription cfgd) {
+
+		// Add extra check to make sure project has Yocto SDK nature before resolving the command launcher
+		try {
+			if (cfgd.getProjectDescription().getProject().getNature(YoctoSDKProjectNature.YoctoSDK_NATURE_ID) == null)
+				return null;
+		} catch (CoreException e) {
+			// Do nothing if project does not exist, or isn't open, or doesn't contain the Yocto SDK nature.
+			return null;
+		}
+
+		return super.getCommandLauncher(cfgd);
+	}
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/ContainerLauncher.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/ContainerLauncher.java
new file mode 100644
index 00000000000..da402a5f37b
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/ContainerLauncher.java
@@ -0,0 +1,1467 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016, 2017 Red Hat Inc., Intel Corporation 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:
+ *     Red Hat - Initial Contribution
+ *     Intel Corporation - add support for Yocto Project CROPS images
+ *******************************************************************************/
+package org.yocto.docker.launcher.ui;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
+import org.eclipse.linuxtools.docker.core.DockerException;
+import org.eclipse.linuxtools.docker.core.IDockerConnection;
+import org.eclipse.linuxtools.docker.core.IDockerContainerConfig;
+import org.eclipse.linuxtools.docker.core.IDockerContainerExit;
+import org.eclipse.linuxtools.docker.core.IDockerContainerInfo;
+import org.eclipse.linuxtools.docker.core.IDockerHostConfig;
+import org.eclipse.linuxtools.docker.core.IDockerImage;
+import org.eclipse.linuxtools.docker.core.IDockerImageInfo;
+import org.eclipse.linuxtools.docker.core.IDockerPortBinding;
+import org.eclipse.linuxtools.docker.ui.Activator;
+import org.eclipse.linuxtools.docker.ui.launch.IContainerLaunchListener;
+import org.eclipse.linuxtools.docker.ui.launch.IErrorMessageHolder;
+import org.eclipse.linuxtools.docker.ui.launch.IRunConsoleListener;
+import org.eclipse.linuxtools.docker.ui.launch.Messages;
+import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.linuxtools.internal.docker.core.DockerConsoleOutputStream;
+import org.eclipse.linuxtools.internal.docker.core.DockerContainerConfig;
+import org.eclipse.linuxtools.internal.docker.core.DockerHostConfig;
+import org.eclipse.linuxtools.internal.docker.core.DockerPortBinding;
+import org.eclipse.linuxtools.internal.docker.core.IConsoleListener;
+import org.eclipse.linuxtools.internal.docker.ui.consoles.ConsoleOutputStream;
+import org.eclipse.linuxtools.internal.docker.ui.consoles.RunConsole;
+import org.eclipse.linuxtools.internal.docker.ui.launch.ContainerCommandProcess;
+import org.eclipse.linuxtools.internal.docker.ui.launch.LaunchConfigurationUtils;
+import org.eclipse.linuxtools.internal.docker.ui.views.DVMessages;
+import org.eclipse.linuxtools.internal.docker.ui.wizards.DataVolumeModel;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ *
+ * @see org.eclipse.linuxtools.docker.ui.launch
+ *
+ */
+ at SuppressWarnings("restriction")
+public class ContainerLauncher {
+
+	private static final String ERROR_CREATING_CONTAINER = "ContainerCreateError.msg"; //$NON-NLS-1$
+	private static final String ERROR_LAUNCHING_CONTAINER = "ContainerLaunchError.msg"; //$NON-NLS-1$
+	private static final String ERROR_NO_CONNECTIONS = "ContainerNoConnections.msg"; //$NON-NLS-1$
+	private static final String ERROR_NO_CONNECTION_WITH_URI = "ContainerNoConnectionWithURI.msg"; //$NON-NLS-1$
+
+	private static final String DIRFILE_NAME = "copiedVolumes"; //$NON-NLS-1$
+
+	private static RunConsole console;
+
+	private static Object lockObject = new Object();
+	private static Map<String, Map<String, Set<String>>> copiedVolumesMap = null;
+
+	private class CopyVolumesJob extends Job {
+
+		private static final String COPY_VOLUMES_JOB_TITLE = "ContainerLaunch.copyVolumesJob.title"; //$NON-NLS-1$
+		private static final String COPY_VOLUMES_DESC = "ContainerLaunch.copyVolumesJob.desc"; //$NON-NLS-1$
+		private static final String COPY_VOLUMES_TASK = "ContainerLaunch.copyVolumesJob.task"; //$NON-NLS-1$
+		private static final String ERROR_COPYING_VOLUME = "ContainerLaunch.copyVolumesJob.error"; //$NON-NLS-1$
+
+		private final Map<String, String> volumes;
+		private final IDockerConnection connection;
+		private final String containerId;
+
+		public CopyVolumesJob(Map<String, String> volumes,
+				IDockerConnection connection,
+				String containerId) {
+			super(Messages.getString(COPY_VOLUMES_JOB_TITLE));
+			this.volumes = volumes;
+			this.connection = connection;
+			this.containerId = containerId;
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			monitor.beginTask(
+					Messages.getFormattedString(COPY_VOLUMES_DESC, containerId),
+					volumes.size());
+			Iterator<String> iterator = volumes.keySet().iterator();
+			IStatus status = Status.OK_STATUS;
+			// for each remote volume, copy from host to Container volume
+			while (iterator.hasNext()) {
+				if (monitor.isCanceled()) {
+					monitor.done();
+					return Status.CANCEL_STATUS;
+				}
+				String hostDirectory = iterator.next();
+				String containerDirectory = volumes.get(hostDirectory);
+				if (!containerDirectory.endsWith("/")) { //$NON-NLS-1$
+					containerDirectory = containerDirectory + "/"; //$NON-NLS-1$
+				}
+				if (!hostDirectory.endsWith("/")) { //$NON-NLS-1$
+					hostDirectory = hostDirectory + "/"; //$NON-NLS-1$
+				}
+				monitor.setTaskName(Messages
+						.getFormattedString(COPY_VOLUMES_TASK, hostDirectory));
+				try {
+					((DockerConnection) connection).copyToContainer(
+							hostDirectory, containerId, containerDirectory);
+					monitor.worked(1);
+				} catch (DockerException | InterruptedException
+						| IOException e) {
+					monitor.done();
+					final String dir = hostDirectory;
+					Display.getDefault().syncExec(() -> MessageDialog.openError(
+							PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+									.getShell(),
+							Messages.getFormattedString(ERROR_COPYING_VOLUME,
+									new String[] { dir, containerId }),
+							e.getMessage()));
+					status = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
+							e.getMessage());
+				} finally {
+					monitor.done();
+				}
+			}
+			return status;
+		}
+
+	}
+
+	private class CopyVolumesFromImageJob extends Job {
+
+		private static final String COPY_VOLUMES_FROM_JOB_TITLE = "ContainerLaunch.copyVolumesFromJob.title"; //$NON-NLS-1$
+		private static final String COPY_VOLUMES_FROM_DESC = "ContainerLaunch.copyVolumesFromJob.desc"; //$NON-NLS-1$
+		private static final String COPY_VOLUMES_FROM_TASK = "ContainerLaunch.copyVolumesFromJob.task"; //$NON-NLS-1$
+
+		private final List<String> volumes;
+		private final IDockerConnection connection;
+		private final String image;
+		private final IPath target;
+		private Set<String> dirList;
+
+		public CopyVolumesFromImageJob(
+				IDockerConnection connection,
+				String image, List<String> volumes, IPath target) {
+			super(Messages.getString(COPY_VOLUMES_FROM_JOB_TITLE));
+			this.volumes = volumes;
+			this.connection = connection;
+			this.image = image;
+			this.target = target;
+			Map<String, Set<String>> dirMap = null;
+			synchronized (lockObject) {
+				String uri = connection.getUri();
+				dirMap = copiedVolumesMap.get(uri);
+				if (dirMap == null) {
+					dirMap = new HashMap<>();
+					copiedVolumesMap.put(uri, dirMap);
+				}
+				dirList = dirMap.get(image);
+				if (dirList == null) {
+					dirList = new HashSet<>();
+					dirMap.put(image, dirList);
+				}
+			}
+		}
+
+		@Override
+		protected IStatus run(final IProgressMonitor monitor) {
+			monitor.beginTask(
+					Messages.getFormattedString(COPY_VOLUMES_FROM_DESC, image),
+					volumes.size());
+			String containerId = null;
+			try {
+				IDockerImage dockerImage = ((DockerConnection) connection)
+						.getImageByTag(image);
+				// if there is a .image_id file, check the image id to ensure
+				// the user hasn't loaded a new version which may have
+				// different header files installed.
+				IPath imageFilePath = target.append(".image_id"); //$NON-NLS-1$
+				File imageFile = imageFilePath.toFile();
+				boolean needImageIdFile = !imageFile.exists();
+				if (!needImageIdFile) {
+					try (FileReader reader = new FileReader(imageFile);
+							BufferedReader bufferReader = new BufferedReader(
+									reader);) {
+						String imageId = bufferReader.readLine();
+						if (!dockerImage.id().equals(imageId)) {
+							// if image id has changed...all bets are off
+							// and we must reload all directories
+							dirList.clear();
+							needImageIdFile = true;
+						}
+					} catch (IOException e) {
+						// ignore
+					}
+				}
+				if (needImageIdFile) {
+					try (FileWriter writer = new FileWriter(imageFile);
+							BufferedWriter bufferedWriter = new BufferedWriter(
+									writer);) {
+						bufferedWriter.write(dockerImage.id());
+						bufferedWriter.newLine();
+					} catch (IOException e) {
+						// ignore
+					}
+				}
+				DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder()
+						.cmd("/bin/sh").image(image); //$NON-NLS-1$
+				IDockerContainerConfig config = builder.build();
+				DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder();
+				IDockerHostConfig hostConfig = hostBuilder.build();
+				containerId = ((DockerConnection) connection)
+						.createContainer(config, hostConfig, null);
+				for (String volume : volumes) {
+					if (monitor.isCanceled()) {
+						monitor.done();
+						return Status.CANCEL_STATUS;
+					}
+					// don't bother copying files from project
+					if (volume.contains("${ProjName}")) { //$NON-NLS-1$
+						monitor.worked(1);
+						continue;
+					}
+					// if we have already copied the directory either directly
+					// or as part of a parent directory copy, then skip to next
+					// volume.
+					for (String path : dirList) {
+						if (volume.equals(path)
+								|| (volume.startsWith(path) && volume.charAt(
+										path.length()) == File.separatorChar)) {
+							monitor.worked(1);
+							continue;
+						}
+					}
+					try {
+						monitor.setTaskName(Messages.getFormattedString(
+								COPY_VOLUMES_FROM_TASK, volume));
+						monitor.worked(1);
+
+
+						InputStream in = ((DockerConnection) connection)
+								.copyContainer(containerId, volume);
+
+						synchronized (lockObject) {
+							dirList.add(volume);
+						}
+
+						/*
+						 * The input stream from copyContainer might be
+						 * incomplete or non-blocking so we should wrap it in a
+						 * stream that is guaranteed to block until data is
+						 * available.
+						 */
+						TarArchiveInputStream k = new TarArchiveInputStream(
+								new BlockingInputStream(in));
+						TarArchiveEntry te = null;
+						target.toFile().mkdirs();
+						IPath currDir = target.append(volume)
+								.removeLastSegments(1);
+						currDir.toFile().mkdirs();
+						while ((te = k.getNextTarEntry()) != null) {
+							long size = te.getSize();
+							IPath path = currDir;
+							path = path.append(te.getName());
+							File f = new File(path.toOSString());
+							if (te.isDirectory()) {
+								f.mkdir();
+								continue;
+							} else {
+								f.createNewFile();
+							}
+							FileOutputStream os = new FileOutputStream(f);
+							int bufferSize = ((int) size > 4096 ? 4096
+									: (int) size);
+							byte[] barray = new byte[bufferSize];
+							int result = -1;
+							while ((result = k.read(barray, 0,
+									bufferSize)) > -1) {
+								if (monitor.isCanceled()) {
+									monitor.done();
+									k.close();
+									os.close();
+									return Status.CANCEL_STATUS;
+								}
+								os.write(barray, 0, result);
+							}
+							os.close();
+						}
+						k.close();
+					} catch (final DockerException e) {
+						// ignore
+					}
+				}
+			} catch (InterruptedException e) {
+				// do nothing
+			} catch (IOException e) {
+				Activator.log(e);
+			} catch (DockerException e1) {
+				Activator.log(e1);
+			} finally {
+				if (containerId != null) {
+					try {
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					} catch (DockerException | InterruptedException e) {
+						// ignore
+					}
+				}
+				monitor.done();
+			}
+			return Status.OK_STATUS;
+		}
+	}
+
+	/**
+	 * A blocking input stream that waits until data is available.
+	 */
+	private class BlockingInputStream extends InputStream {
+		private InputStream in;
+
+		public BlockingInputStream(InputStream in) {
+			this.in = in;
+		}
+
+		@Override
+		public int read() throws IOException {
+			return in.read();
+		}
+	}
+
+	@Override
+	protected void finalize() throws Throwable {
+		synchronized (lockObject) {
+			if (copiedVolumesMap != null) {
+				IPath pluginPath = Platform.getStateLocation(
+						Platform.getBundle(Activator.PLUGIN_ID));
+				IPath path = pluginPath.append(DIRFILE_NAME);
+
+				File dirFile = path.toFile();
+				FileOutputStream f = new FileOutputStream(dirFile);
+				try (ObjectOutputStream oos = new ObjectOutputStream(f)) {
+					oos.writeObject(copiedVolumesMap);
+				}
+			}
+		}
+		super.finalize();
+	}
+
+	public ContainerLauncher() {
+		initialize();
+	}
+
+	@SuppressWarnings("unchecked")
+	private void initialize() {
+		synchronized (lockObject) {
+			if (copiedVolumesMap == null) {
+				IPath pluginPath = Platform.getStateLocation(
+						Platform.getBundle(Activator.PLUGIN_ID));
+				IPath path = pluginPath.append(DIRFILE_NAME);
+
+				File dirFile = path.toFile();
+				if (dirFile.exists()) {
+					try (FileInputStream f = new FileInputStream(dirFile)) {
+						try (ObjectInputStream ois = new ObjectInputStream(f)) {
+							copiedVolumesMap = (Map<String, Map<String, Set<String>>>) ois
+									.readObject();
+						} catch (ClassNotFoundException
+								| FileNotFoundException e) {
+							// should never happen so print stack trace
+							e.printStackTrace();
+						}
+					} catch (IOException e) {
+						// will handle this below
+					}
+				}
+			}
+			if (copiedVolumesMap == null) {
+				copiedVolumesMap = new HashMap<>();
+			}
+		}
+	}
+
+	/**
+	 * Perform a launch of a command in a container and output stdout/stderr to
+	 * console.
+	 *
+	 * @param id
+	 *            - id of caller to use to distinguish console owner
+	 * @param listener
+	 *            - optional listener of the run console
+	 * @param connectionUri
+	 *            - the specified connection to use
+	 * @param image
+	 *            - the image to use
+	 * @param command
+	 *            - command to run
+	 * @param commandDir
+	 *            - directory command requires or null
+	 * @param workingDir
+	 *            - working directory or null
+	 * @param additionalDirs
+	 *            - additional directories to mount or null
+	 * @param origEnv
+	 *            - original environment if we are appending to our existing
+	 *            environment
+	 * @param envMap
+	 *            - map of environment variable settings
+	 * @param ports
+	 *            - ports to expose
+	 * @param keep
+	 *            - keep container after running
+	 * @param stdinSupport
+	 *            - true if stdin support is required, false otherwise
+	 */
+	public void launch(String id, IContainerLaunchListener listener,
+			final String connectionUri,
+			String image, String command, String commandDir, String workingDir,
+			List<String> additionalDirs, Map<String, String> origEnv,
+			Map<String, String> envMap, List<String> ports, boolean keep,
+			boolean stdinSupport) {
+		launch(id, listener, connectionUri, image, command, commandDir,
+				workingDir, additionalDirs, origEnv, envMap, ports, keep,
+				stdinSupport, false);
+	}
+
+	/**
+	 * Perform a launch of a command in a container and output stdout/stderr to
+	 * console.
+	 *
+	 * @param id
+	 *            - id of caller to use to distinguish console owner
+	 * @param listener
+	 *            - optional listener of the run console
+	 * @param connectionUri
+	 *            - the specified connection to use
+	 * @param image
+	 *            - the image to use
+	 * @param command
+	 *            - command to run
+	 * @param commandDir
+	 *            - directory command requires or null
+	 * @param workingDir
+	 *            - working directory or null
+	 * @param additionalDirs
+	 *            - additional directories to mount or null
+	 * @param origEnv
+	 *            - original environment if we are appending to our existing
+	 *            environment
+	 * @param envMap
+	 *            - map of environment variable settings
+	 * @param ports
+	 *            - ports to expose
+	 * @param keep
+	 *            - keep container after running
+	 * @param stdinSupport
+	 *            - true if stdin support is required, false otherwise
+	 * @param privilegedMode
+	 *            - true if privileged mode is required, false otherwise
+	 * @since 2.1
+	 */
+	public void launch(String id, IContainerLaunchListener listener,
+			final String connectionUri, String image, String command,
+			String commandDir, String workingDir, List<String> additionalDirs,
+			Map<String, String> origEnv, Map<String, String> envMap,
+			List<String> ports, boolean keep, boolean stdinSupport,
+			boolean privilegedMode) {
+		launch(id, listener, connectionUri, image, command, commandDir,
+				workingDir, additionalDirs, origEnv, envMap, ports, keep,
+				stdinSupport, privilegedMode, null);
+	}
+
+	/**
+	 * Perform a launch of a command in a container and output stdout/stderr to
+	 * console.
+	 *
+	 * @param id
+	 *            - id of caller to use to distinguish console owner
+	 * @param listener
+	 *            - optional listener of the run console
+	 * @param connectionUri
+	 *            - the specified connection to use
+	 * @param image
+	 *            - the image to use
+	 * @param command
+	 *            - command to run
+	 * @param commandDir
+	 *            - directory command requires or null
+	 * @param workingDir
+	 *            - working directory or null
+	 * @param additionalDirs
+	 *            - additional directories to mount or null
+	 * @param origEnv
+	 *            - original environment if we are appending to our existing
+	 *            environment
+	 * @param envMap
+	 *            - map of environment variable settings
+	 * @param ports
+	 *            - ports to expose
+	 * @param keep
+	 *            - keep container after running
+	 * @param stdinSupport
+	 *            - true if stdin support is required, false otherwise
+	 * @param privilegedMode
+	 *            - true if privileged mode is required, false otherwise
+	 * @param labels
+	 *            - Map of labels for the container
+	 * @since 2.2
+	 */
+	public void launch(String id, IContainerLaunchListener listener,
+			final String connectionUri, String image, String command,
+			String commandDir, String workingDir, List<String> additionalDirs,
+			Map<String, String> origEnv, Map<String, String> envMap,
+			List<String> ports, boolean keep, boolean stdinSupport,
+			boolean privilegedMode, Map<String, String> labels) {
+		launch(id, listener, connectionUri, image, command, commandDir,
+				workingDir, additionalDirs, origEnv, envMap, ports, keep,
+				stdinSupport, privilegedMode, labels, null);
+
+	}
+
+	// The following class allows us to use internal IConsoleListeners in
+	// docker.core
+	// but still use the public IRunConsoleListeners API here without requiring
+	// a minor release.
+	private class RunConsoleListenerBridge implements IConsoleListener {
+
+		private IRunConsoleListener listener;
+
+		public RunConsoleListenerBridge(IRunConsoleListener listener) {
+			this.listener = listener;
+		}
+
+		@Override
+		public void newOutput(String output) {
+			listener.newOutput(output);
+		}
+
+	}
+
+	/**
+	 * Perform a launch of a command in a container and output stdout/stderr to
+	 * console.
+	 *
+	 * @param id
+	 *            - id of caller to use to distinguish console owner
+	 * @param listener
+	 *            - optional listener of the run console
+	 * @param connectionUri
+	 *            - the specified connection to use
+	 * @param image
+	 *            - the image to use
+	 * @param command
+	 *            - command to run
+	 * @param commandDir
+	 *            - directory command requires or null
+	 * @param workingDir
+	 *            - working directory or null
+	 * @param additionalDirs
+	 *            - additional directories to mount or null
+	 * @param origEnv
+	 *            - original environment if we are appending to our existing
+	 *            environment
+	 * @param envMap
+	 *            - map of environment variable settings
+	 * @param ports
+	 *            - ports to expose
+	 * @param keep
+	 *            - keep container after running
+	 * @param stdinSupport
+	 *            - true if stdin support is required, false otherwise
+	 * @param privilegedMode
+	 *            - true if privileged mode is required, false otherwise
+	 * @param labels
+	 *            - Map of labels for the container
+	 * @param seccomp
+	 *            - seccomp profile
+	 * @since 3.0
+	 */
+	public void launch(String id, IContainerLaunchListener listener,
+			final String connectionUri, String image, String command,
+			String commandDir, String workingDir, List<String> additionalDirs,
+			Map<String, String> origEnv, Map<String, String> envMap,
+			List<String> ports, boolean keep, boolean stdinSupport,
+			boolean privilegedMode, Map<String, String> labels,
+			String seccomp) {
+
+		final String LAUNCH_TITLE = "ContainerLaunch.title"; //$NON-NLS-1$
+		final String LAUNCH_EXITED_TITLE = "ContainerLaunchExited.title"; //$NON-NLS-1$
+
+		final List<String> env = new ArrayList<>();
+		env.addAll(toList(origEnv));
+		env.addAll(toList(envMap));
+
+
+		final List<String> cmdList = getCmdList(command);
+
+		final Set<String> exposedPorts = new HashSet<>();
+		final Map<String, List<IDockerPortBinding>> portBindingsMap = new HashMap<>();
+
+		if (ports != null) {
+			for (String port : ports) {
+				port = port.trim();
+				if (port.length() > 0) {
+					String[] segments = port.split(":"); //$NON-NLS-1$
+					if (segments.length == 1) { // containerPort
+						exposedPorts.add(segments[0]);
+						portBindingsMap
+								.put(segments[0],
+										Arrays.asList((IDockerPortBinding) new DockerPortBinding(
+												"", ""))); //$NON-NLS-1$ //$NON-NLS-2$
+					} else if (segments.length == 2) { // hostPort:containerPort
+						exposedPorts.add(segments[1]);
+						portBindingsMap
+								.put(segments[1],
+										Arrays.asList((IDockerPortBinding) new DockerPortBinding(
+												"", segments[0]))); //$NON-NLS-1$ //$NON-NLS-2$
+					} else if (segments.length == 3) { // either
+						// ip:hostPort:containerPort
+						// or ip::containerPort
+						exposedPorts.add(segments[1]);
+						if (segments[1].isEmpty()) {
+							portBindingsMap
+									.put(segments[2],
+											Arrays.asList((IDockerPortBinding) new DockerPortBinding(
+													"", segments[0]))); //$NON-NLS-1$ //$NON-NLS-2$
+						} else {
+							portBindingsMap
+									.put(segments[2],
+											Arrays.asList((IDockerPortBinding) new DockerPortBinding(
+													segments[0], segments[1]))); //$NON-NLS-1$ //$NON-NLS-2$
+						}
+					}
+				}
+			}
+
+		}
+
+		// Note we only pass volumes to the config if we have a
+		// remote daemon. Local mounted volumes are passed
+		// via the HostConfig binds setting
+
+		DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder()
+				.openStdin(stdinSupport)
+				.cmd(cmdList)
+				.image(image)
+				.workingDir(workingDir);
+
+		// Ugly hack...we want CDT gdbserver to run in the terminal so we look
+		// for its
+		// ContainerListener class and set tty=true in that case...this avoids a
+		// minor release and we can later add a new launch method with the tty
+		// option
+		if (listener != null && listener.getClass().getName().equals(
+				"org.eclipse.cdt.internal.docker.launcher.ContainerLaunchConfigurationDelegate$StartGdbServerJob")) {
+			builder = builder.tty(true);
+		}
+
+		// add any exposed ports as needed
+		if (exposedPorts.size() > 0)
+			builder = builder.exposedPorts(exposedPorts);
+
+		// add any labels if specified
+		if (labels != null)
+			builder = builder.labels(labels);
+
+		if (!DockerConnectionManager.getInstance().hasConnections()) {
+			Display.getDefault()
+					.syncExec(() -> MessageDialog.openError(
+							PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+									.getShell(),
+							DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
+							DVMessages.getString(ERROR_NO_CONNECTIONS)));
+			return;
+		}
+
+		// Try and use the specified connection that was used before,
+		// otherwise, open an error
+		final IDockerConnection connection = DockerConnectionManager
+				.getInstance().getConnectionByUri(connectionUri);
+		if (connection == null) {
+			Display.getDefault()
+					.syncExec(() -> MessageDialog.openError(
+							PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+									.getShell(),
+							DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
+							DVMessages.getFormattedString(
+									ERROR_NO_CONNECTION_WITH_URI,
+									connectionUri)));
+			return;
+		}
+
+		// if connection is not open, force it to be by fetching images
+		if (!connection.isOpen()) {
+			connection.getImages();
+		}
+
+		DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder()
+				.privileged(privilegedMode);
+
+		// specify seccomp profile if caller has provided one - needed to use
+		// ptrace with gdbserver
+		if (seccomp != null) {
+			hostBuilder.securityOpt(seccomp);
+		}
+
+
+		final Map<String, String> remoteVolumes = new HashMap<>();
+		if (!((DockerConnection) connection).isLocal()) {
+			final Set<String> volumes = new HashSet<>();
+			// if using remote daemon, we have to
+			// handle volume mounting differently.
+			// Instead we mount empty volumes and copy
+			// the host data over before starting.
+			if (additionalDirs != null) {
+				for (String dir : additionalDirs) {
+					remoteVolumes.put(dir, dir);
+					volumes.add(dir);
+				}
+			}
+			if (workingDir != null) {
+				remoteVolumes.put(workingDir, workingDir); // $NON-NLS-1$
+				volumes.add(workingDir);
+			}
+			if (commandDir != null) {
+				remoteVolumes.put(commandDir, commandDir); // $NON-NLS-1$
+				volumes.add(commandDir);
+			}
+			builder = builder.volumes(volumes);
+		} else {
+			// Running daemon on local host.
+			// Add mounts for any directories we need to run the executable.
+			// When we add mount points, we need entries of the form:
+			// hostname:mountname:Z.
+			// In our case, we want all directories mounted as-is so the
+			// executable will run as the user expects.
+			final List<String> volumes = new ArrayList<>();
+			if (additionalDirs != null) {
+				for (String dir : additionalDirs) {
+					volumes.add(dir + ":" + dir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
+				}
+			}
+			if (workingDir != null) {
+				volumes.add(workingDir + ":" + workingDir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			if (commandDir != null) {
+				volumes.add(commandDir + ":" + commandDir + ":Z"); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			hostBuilder = hostBuilder.binds(volumes);
+		}
+
+		final DockerContainerConfig config = builder.build();
+
+		// add any port bindings if specified
+		if (portBindingsMap.size() > 0)
+			hostBuilder = hostBuilder.portBindings(portBindingsMap);
+
+		final IDockerHostConfig hostConfig = hostBuilder.build();
+
+		final String imageName = image;
+		final boolean keepContainer = keep;
+		final String consoleId = id;
+		final IContainerLaunchListener containerListener = listener;
+
+		Thread t = new Thread(() -> {
+			// create the container
+			String containerId = null;
+			try {
+				containerId = ((DockerConnection) connection)
+						.createContainer(config, hostConfig, null);
+				if (!((DockerConnection) connection).isLocal()) {
+					// if daemon is remote, we need to copy
+					// data over from the host.
+					if (!remoteVolumes.isEmpty()) {
+						CopyVolumesJob job = new CopyVolumesJob(remoteVolumes,
+								connection, containerId);
+						job.schedule();
+						job.join();
+						if (job.getResult() != Status.OK_STATUS)
+							return;
+					}
+				}
+				if (config.tty()) {
+					// We need tty support to handle issue with Docker daemon
+					// not always outputting in time (e.g. we might get an
+					// output line after the process has exited which can be
+					// too late to show or it might get displayed in a wrong
+					// order in relation to other output. We also want the
+					// output to ultimately show up in the Console View.
+					OutputStream stream = null;
+					RunConsole oldConsole = getConsole();
+					final RunConsole rc = RunConsole.findConsole(containerId,
+							consoleId);
+					setConsole(rc);
+					rc.clearConsole();
+					if (oldConsole != null)
+						RunConsole.removeConsole(oldConsole);
+					Display.getDefault().syncExec(() -> rc.setTitle(Messages
+							.getFormattedString(LAUNCH_TITLE, new String[] {
+									cmdList.get(0), imageName })));
+					if (rc != null) {
+						stream = rc.getOutputStream();
+					}
+
+					// We want terminal support, but we want to output to the
+					// RunConsole.
+					// To do this, we create a DockerConsoleOutputStream which
+					// we
+					// hook into the TM Terminal via stdout and stderr output
+					// listeners.
+					// These listeners will output to the
+					// DockerConsoleOutputStream which
+					// will in turn output to the RunConsole. See
+					// DockerConnection.openTerminal().
+					DockerConsoleOutputStream out = new DockerConsoleOutputStream(
+							stream);
+					RunConsole.attachToTerminal(connection, containerId, out);
+					if (containerListener != null) {
+						out.addConsoleListener(new RunConsoleListenerBridge(
+								containerListener));
+					}
+					((DockerConnection) connection).startContainer(containerId,
+							null, null);
+					IDockerContainerInfo info = ((DockerConnection) connection)
+							.getContainerInfo(containerId);
+					if (containerListener != null) {
+						containerListener.containerInfo(info);
+					}
+					// Wait for the container to finish
+					final IDockerContainerExit status = ((DockerConnection) connection)
+							.waitForContainer(containerId);
+					Display.getDefault().syncExec(() -> {
+						rc.setTitle(
+								Messages.getFormattedString(LAUNCH_EXITED_TITLE,
+										new String[] {
+												status.statusCode().toString(),
+												cmdList.get(0), imageName }));
+						rc.showConsole();
+						// We used a TM Terminal to receive the output of the
+						// session and
+						// then sent the output to the RunConsole. Remove the
+						// terminal
+						// tab that got created now that we are finished and all
+						// data is shown
+						// in Console View.
+						IWorkbenchPage page = PlatformUI.getWorkbench()
+								.getActiveWorkbenchWindow().getActivePage();
+						IViewPart terminalView = page.findView(
+								"org.eclipse.tm.terminal.view.ui.TerminalsView");
+						CTabFolder ctabfolder = terminalView
+								.getAdapter(CTabFolder.class);
+						if (ctabfolder != null) {
+							CTabItem[] items = ctabfolder.getItems();
+							for (CTabItem item : items) {
+								if (item.getText().endsWith(info.name())) {
+									item.dispose();
+									break;
+								}
+							}
+						 }
+					});
+					// Let any container listener know that the container is
+					// finished
+					if (containerListener != null)
+						containerListener.done();
+
+					if (!keepContainer) {
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					}
+				} else {
+					OutputStream stream = null;
+					RunConsole oldConsole = getConsole();
+					final RunConsole rc = RunConsole.findConsole(containerId,
+							consoleId);
+					setConsole(rc);
+					rc.clearConsole();
+					if (oldConsole != null)
+						RunConsole.removeConsole(oldConsole);
+					Display.getDefault().syncExec(() -> rc.setTitle(Messages
+							.getFormattedString(LAUNCH_TITLE, new String[] {
+									cmdList.get(0), imageName })));
+					// if (!rc.isAttached()) {
+					rc.attachToConsole(connection, containerId);
+					// }
+					if (rc != null) {
+						stream = rc.getOutputStream();
+						if (containerListener != null) {
+							((ConsoleOutputStream) stream)
+									.addConsoleListener(containerListener);
+						}
+					}
+					// Create a unique logging thread id which has container id
+					// and console id
+					String loggingId = containerId + "." + consoleId;
+					((DockerConnection) connection).startContainer(containerId,
+							loggingId, stream);
+					if (rc != null)
+						rc.showConsole();
+					if (containerListener != null) {
+						IDockerContainerInfo info = ((DockerConnection) connection)
+								.getContainerInfo(containerId);
+						containerListener.containerInfo(info);
+					}
+
+					// Wait for the container to finish
+					final IDockerContainerExit status = ((DockerConnection) connection)
+							.waitForContainer(containerId);
+					Display.getDefault().syncExec(() -> {
+						rc.setTitle(
+								Messages.getFormattedString(LAUNCH_EXITED_TITLE,
+										new String[] {
+												status.statusCode().toString(),
+												cmdList.get(0), imageName }));
+						rc.showConsole();
+					});
+
+					// Let any container listener know that the container is
+					// finished
+					if (containerListener != null)
+						containerListener.done();
+
+					if (!keepContainer) {
+						// Drain the logging thread before we remove the
+						// container (we need to use the logging id)
+						Thread.sleep(1000);
+						((DockerConnection) connection)
+								.stopLoggingThread(loggingId);
+						// Look for any Display Log console that the user may
+						// have opened which would be
+						// separate and make sure it is removed as well
+						RunConsole rc2 = RunConsole
+								.findConsole(((DockerConnection) connection)
+										.getContainer(containerId));
+						if (rc2 != null)
+							RunConsole.removeConsole(rc2);
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					}
+				}
+
+			} catch (final DockerException e2) {
+				// error in creation, try and remove Container if possible
+				if (!keepContainer && containerId != null) {
+					try {
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					} catch (DockerException | InterruptedException e1) {
+						// ignore exception
+					}
+				}
+				Display.getDefault().syncExec(() -> MessageDialog.openError(
+						PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+								.getShell(),
+						DVMessages.getFormattedString(ERROR_CREATING_CONTAINER,
+								imageName),
+						e2.getMessage()));
+			} catch (InterruptedException e3) {
+				// for now
+				// do nothing
+			}
+			((DockerConnection) connection).getContainers(true);
+		});
+		t.start();
+	}
+
+	/**
+	 * Fetch directories from Container and place them in a specified location.
+	 *
+	 * @param connectionUri
+	 *            - uri of connection to use
+	 * @param imageName
+	 *            - name of image to use
+	 * @param containerDirs
+	 *            - list of directories to copy
+	 * @param hostDir
+	 *            - host directory to copy directories to
+	 * @return 0 if successful, -1 if failure occurred
+	 *
+	 * @since 3.0
+	 */
+	public int fetchContainerDirs(String connectionUri, String imageName,
+			List<String> containerDirs, IPath hostDir) {
+		// Try and use the specified connection that was used before,
+		// otherwise, open an error
+		final IDockerConnection connection = DockerConnectionManager
+				.getInstance().getConnectionByUri(connectionUri);
+		if (connection == null) {
+			Display.getDefault()
+					.syncExec(() -> MessageDialog.openError(
+							PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+									.getShell(),
+							DVMessages.getString(ERROR_LAUNCHING_CONTAINER),
+							DVMessages.getFormattedString(
+									ERROR_NO_CONNECTION_WITH_URI,
+									connectionUri)));
+			return -1;
+		}
+
+		CopyVolumesFromImageJob job = new CopyVolumesFromImageJob(connection,
+				imageName, containerDirs, hostDir);
+		job.schedule();
+		return 0;
+	}
+
+	/**
+	 * Create a Process to run an arbitrary command in a Container with uid of
+	 * caller so any files created are accessible to user.
+	 *
+	 * @param connectionName
+	 *            - uri of connection to use
+	 * @param imageName
+	 *            - name of image to use
+	 * @param project
+	 *            - Eclipse project
+	 * @param errMsgHolder
+	 *            - holder for any error messages
+	 * @param command
+	 *            - command to run
+	 * @param commandDir
+	 *            - directory path to command
+	 * @param workingDir
+	 *            - where to run command
+	 * @param additionalDirs
+	 *            - additional directories to mount
+	 * @param origEnv
+	 *            - original environment if we are appending to existing
+	 * @param envMap
+	 *            - new environment
+	 * @param supportStdin
+	 *            - support using stdin
+	 * @param privilegedMode
+	 *            - run in privileged mode
+	 * @param labels
+	 *            - labels to apply to Container
+	 * @param keepContainer
+	 *            - boolean whether to keep Container when done
+	 * @param uid
+	 *            - the user id for the container process
+	 * @return Process that can be used to check for completion and for routing
+	 *         stdout/stderr
+	 *
+	 * @since 3.0
+	 */
+	public Process runCommand(String connectionName, String imageName, IProject project,
+			IErrorMessageHolder errMsgHolder, String command,
+			String commandDir,
+			String workingDir,
+			List<String> additionalDirs, Map<String, String> origEnv,
+			Properties envMap, boolean supportStdin,
+			boolean privilegedMode, HashMap<String, String> labels,
+			boolean keepContainer, Integer uid) {
+
+		final List<String> env = new ArrayList<>();
+		env.addAll(toList(origEnv));
+		env.addAll(toList(envMap));
+
+		final List<String> cmdList = getCmdList(command);
+
+		final Map<String, List<IDockerPortBinding>> portBindingsMap = new HashMap<>();
+
+
+		IDockerConnection[] connections = DockerConnectionManager
+				.getInstance().getConnections();
+		if (connections == null || connections.length == 0) {
+			errMsgHolder.setErrorMessage(
+					Messages.getString("ContainerLaunch.noConnections.error")); //$NON-NLS-1$
+			return null;
+		}
+
+		IDockerConnection connection = null;
+		for (IDockerConnection c : connections) {
+			if (c.getUri().equals(connectionName)) {
+				connection = c;
+				break;
+			}
+		}
+
+		if (connection == null) {
+			errMsgHolder.setErrorMessage(Messages.getFormattedString(
+					"ContainerLaunch.connectionNotFound.error", //$NON-NLS-1$
+					connectionName));
+			return null;
+		}
+
+		List<IDockerImage> images = connection.getImages();
+		if (images.isEmpty()) {
+			errMsgHolder.setErrorMessage(
+					Messages.getString("ContainerLaunch.noImages.error")); //$NON-NLS-1$
+			return null;
+		}
+
+		IDockerImageInfo info = connection.getImageInfo(imageName);
+		if (info == null) {
+			errMsgHolder.setErrorMessage(Messages.getFormattedString(
+					"ContainerLaunch.imageNotFound.error", imageName)); //$NON-NLS-1$
+			return null;
+		}
+
+		DockerContainerConfig.Builder builder = new DockerContainerConfig.Builder()
+				.openStdin(supportStdin).cmd(cmdList).image(imageName)
+				.workingDir(workingDir);
+
+		// switch to user id for Linux so output is accessible
+		if (uid != null) {
+			builder = builder.user(uid.toString());
+		}
+
+		// TODO: add group id here when supported by DockerHostConfig.Builder
+
+		// add any labels if specified
+		if (labels != null)
+			builder = builder.labels(labels);
+
+		DockerHostConfig.Builder hostBuilder = new DockerHostConfig.Builder()
+				.privileged(privilegedMode);
+
+		// Note we only pass volumes to the config if we have a
+		// remote daemon. Local mounted volumes are passed
+		// via the HostConfig binds setting
+		final Set<String> remoteVolumes = new TreeSet<>();
+		final Map<String, String> remoteDataVolumes = new HashMap<>();
+		final Set<String> readOnlyVolumes = new TreeSet<>();
+		if (!((DockerConnection) connection).isLocal()) {
+			// if using remote daemon, we have to
+			// handle volume mounting differently.
+			// Instead we mount empty volumes and copy
+			// the host data over before starting.
+			if (additionalDirs != null) {
+				for (String dir : additionalDirs) {
+					IPath p = new Path(dir).removeTrailingSeparator();
+					remoteVolumes.add(p.toPortableString());
+					remoteDataVolumes.put(p.toPortableString(),
+							p.toPortableString());
+					if (dir.contains(":")) { //$NON-NLS-1$
+						DataVolumeModel dvm = DataVolumeModel.parseString(dir);
+						switch (dvm.getMountType()) {
+						case HOST_FILE_SYSTEM:
+							dir = dvm.getHostPathMount();
+							remoteDataVolumes.put(dir, dvm.getContainerMount());
+							// keep track of read-only volumes so we don't copy
+							// these
+							// back after command completion
+							if (dvm.isReadOnly()) {
+								readOnlyVolumes.add(dir);
+							}
+							break;
+						default:
+							continue;
+						}
+					}
+				}
+			}
+			if (workingDir != null) {
+				IPath p = new Path(workingDir).removeTrailingSeparator();
+				remoteVolumes.add(p.toPortableString());
+				remoteDataVolumes.put(p.toPortableString(),
+						p.toPortableString());
+			}
+			if (commandDir != null) {
+				IPath p = new Path(commandDir).removeTrailingSeparator();
+				remoteVolumes.add(p.toPortableString());
+				remoteDataVolumes.put(p.toPortableString(),
+						p.toPortableString());
+			}
+			builder = builder.volumes(remoteVolumes);
+		} else {
+			// Running daemon on local host.
+			// Add mounts for any directories we need to run the executable.
+			// When we add mount points, we need entries of the form:
+			// hostname:mountname:Z.
+			// In our case, we want all directories mounted as-is so the
+			// executable will run as the user expects.
+			final Set<String> volumes = new TreeSet<>();
+			final List<String> volumesFrom = new ArrayList<>();
+			if (additionalDirs != null) {
+				for (String dir : additionalDirs) {
+					IPath p = new Path(dir).removeTrailingSeparator();
+					if (dir.contains(":")) { //$NON-NLS-1$
+						DataVolumeModel dvm = DataVolumeModel.parseString(dir);
+						switch (dvm.getMountType()) {
+						case HOST_FILE_SYSTEM:
+							String bind = LaunchConfigurationUtils
+									.convertToUnixPath(dvm.getHostPathMount())
+									+ ':' + dvm.getContainerPath() + ":Z"; //$NON-NLS-1$ //$NON-NLS-2$
+							if (dvm.isReadOnly()) {
+								bind += ",ro"; //$NON-NLS-1$
+							}
+							volumes.add(bind);
+							break;
+						case CONTAINER:
+							volumesFrom.add(dvm.getContainerMount());
+							break;
+						default:
+							break;
+
+						}
+					} else {
+						volumes.add(p.toPortableString() + ":" //$NON-NLS-1$
+								+ p.toPortableString() + ":Z"); //$NON-NLS-1$
+					}
+				}
+			}
+			if (workingDir != null) {
+				IPath p = new Path(workingDir).removeTrailingSeparator();
+				volumes.add(p.toPortableString() + ":" + p.toPortableString() //$NON-NLS-1$
+						+ ":Z"); //$NON-NLS-1$
+			}
+			if (commandDir != null) {
+				IPath p = new Path(commandDir).removeTrailingSeparator();
+				volumes.add(p.toPortableString() + ":" + p.toPortableString() //$NON-NLS-1$
+						+ ":Z"); //$NON-NLS-1$
+			}
+			List<String> volumeList = new ArrayList<>(volumes);
+			hostBuilder = hostBuilder.binds(volumeList);
+			if (!volumesFrom.isEmpty()) {
+				hostBuilder = hostBuilder.volumesFrom(volumesFrom);
+			}
+		}
+
+		final DockerContainerConfig config = builder.build();
+
+		// add any port bindings if specified
+		if (portBindingsMap.size() > 0)
+			hostBuilder = hostBuilder.portBindings(portBindingsMap);
+
+		final IDockerHostConfig hostConfig = hostBuilder.build();
+
+		// create the container
+		String containerId = null;
+		try {
+			containerId = ((DockerConnection) connection)
+					.createContainer(config, hostConfig, null);
+		} catch (DockerException | InterruptedException e) {
+			errMsgHolder.setErrorMessage(e.getMessage());
+			return null;
+		}
+
+		final String id = containerId;
+		final IDockerConnection conn = connection;
+		if (!((DockerConnection) conn).isLocal()) {
+			// if daemon is remote, we need to copy
+			// data over from the host.
+			if (!remoteVolumes.isEmpty()) {
+				CopyVolumesJob job = new CopyVolumesJob(remoteDataVolumes, conn,
+						id);
+				job.schedule();
+				try {
+					job.join();
+				} catch (InterruptedException e) {
+					// ignore
+				}
+			}
+		}
+		try {
+			((DockerConnection) conn).startContainer(id, null);
+		} catch (DockerException | InterruptedException e) {
+			Activator.log(e);
+		}
+
+		// remove all read-only remote volumes from our list of remote
+		// volumes so they won't be copied back on command completion
+		for (String readonly : readOnlyVolumes) {
+			remoteDataVolumes.remove(readonly);
+		}
+		return new ContainerCommandProcess(connection, imageName, containerId,
+				remoteDataVolumes,
+				keepContainer);
+	}
+
+	/**
+	 * Clean up the container used for launching
+	 *
+	 * @param connectionUri
+	 *            the URI of the connection used
+	 * @param info
+	 *            the container info
+	 */
+	public void cleanup(String connectionUri, IDockerContainerInfo info) {
+		if (!DockerConnectionManager.getInstance().hasConnections()) {
+			return;
+		}
+
+		// Try and find the specified connection
+		final IDockerConnection connection = DockerConnectionManager
+				.getInstance().getConnectionByUri(connectionUri);
+		if (connection == null) {
+			return;
+		}
+		try {
+			connection.killContainer(info.id());
+		} catch (DockerException | InterruptedException e) {
+			// do nothing
+		}
+	}
+
+	/**
+	 * Get the reusable run console for running C/C++ executables in containers.
+	 *
+	 * @return
+	 */
+	private RunConsole getConsole() {
+		return console;
+	}
+
+	private void setConsole(RunConsole cons) {
+		console = cons;
+	}
+
+	/**
+	 * Take the command string and parse it into a list of strings.
+	 *
+	 * @param s
+	 * @return list of strings
+	 */
+	private List<String> getCmdList(String s) {
+		ArrayList<String> list = new ArrayList<>();
+		int length = s.length();
+		boolean insideQuote1 = false; // single-quote
+		boolean insideQuote2 = false; // double-quote
+		boolean escaped = false;
+		StringBuffer buffer = new StringBuffer();
+		// Parse the string and break it up into chunks that are
+		// separated by white-space or are quoted. Ignore characters
+		// that have been escaped, including the escape character.
+		for (int i = 0; i < length; ++i) {
+			char c = s.charAt(i);
+			if (escaped) {
+				buffer.append(c);
+				escaped = false;
+			}
+			switch (c) {
+			case '\'':
+				if (!insideQuote2)
+					insideQuote1 = insideQuote1 ^ true;
+				else
+					buffer.append(c);
+				break;
+			case '\"':
+				if (!insideQuote1)
+					insideQuote2 = insideQuote2 ^ true;
+				else
+					buffer.append(c);
+				break;
+			case '\\':
+				escaped = true;
+				break;
+			case ' ':
+			case '\t':
+			case '\r':
+			case '\n':
+				if (insideQuote1 || insideQuote2)
+					buffer.append(c);
+				else {
+					String item = buffer.toString();
+					buffer.setLength(0);
+					if (item.length() > 0)
+						list.add(item);
+				}
+				break;
+			default:
+				buffer.append(c);
+				break;
+			}
+		}
+		// add last item of string that will be in the buffer
+		String item = buffer.toString();
+		if (item.length() > 0)
+			list.add(item);
+		return list;
+	}
+
+	/**
+	 * Convert map of environment variables to a {@link List} of KEY=VALUE
+	 * String
+	 *
+	 * @param variables
+	 *            the entries to manipulate
+	 * @return the concatenated key/values for each given variable entry
+	 */
+	private List<String> toList(
+			@SuppressWarnings("rawtypes") final Map variables) {
+		final List<String> result = new ArrayList<>();
+		if (variables != null) {
+			@SuppressWarnings({ "unchecked", "rawtypes" })
+			Set<Map.Entry> entries = variables.entrySet();
+			for (@SuppressWarnings("rawtypes")
+			Map.Entry entry : entries) {
+				final String key = (String) entry.getKey();
+				final String value = (String) entry.getValue();
+
+				final String envEntry = key + "=" + value; //$NON-NLS-1$
+				result.add(envEntry);
+			}
+		}
+		return result;
+
+	}
+
+	/**
+	 * Get set of volumes that have been copied from Container to Host as part
+	 * of fetchContainerDirs method.
+	 *
+	 * @param connectionName
+	 *            - uri of connection used
+	 * @param imageName
+	 *            - name of image used
+	 * @return set of paths copied from Container to Host
+	 *
+	 * @since 3.0
+	 */
+	public Set<String> getCopiedVolumes(String connectionName,
+			String imageName) {
+		Set<String> copiedSet = new HashSet<>();
+		if (copiedVolumesMap != null) {
+			Map<String, Set<String>> connectionMap = copiedVolumesMap
+					.get(connectionName);
+			if (connectionMap != null) {
+				Set<String> imageSet = connectionMap.get(imageName);
+				if (imageSet != null) {
+					copiedSet = imageSet;
+				}
+			}
+		}
+		return copiedSet;
+	}
+
+}
diff --git a/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/PokyContainerLauncher.java b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/PokyContainerLauncher.java
new file mode 100644
index 00000000000..aa2cbef9165
--- /dev/null
+++ b/plugins/org.yocto.docker.launcher/src/org/yocto/docker/launcher/ui/PokyContainerLauncher.java
@@ -0,0 +1,25 @@
+package org.yocto.docker.launcher.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.linuxtools.docker.ui.launch.IErrorMessageHolder;
+
+public class PokyContainerLauncher extends ContainerLauncher {
+
+	@Override
+	public Process runCommand(String connectionName, String imageName, IProject project,
+			IErrorMessageHolder errMsgHolder, String command, String commandDir, String workingDir,
+			List<String> additionalDirs, Map<String, String> origEnv, Properties envMap, boolean supportStdin,
+			boolean privilegedMode, HashMap<String, String> labels, boolean keepContainer, Integer uid) {
+
+		String pokyEntryCommand = "--workdir=" + workingDir + " --cmd=\"" + command + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+		return super.runCommand(connectionName, imageName, project, errMsgHolder, pokyEntryCommand, commandDir, workingDir,
+				additionalDirs, origEnv, envMap, supportStdin, privilegedMode, labels, keepContainer, uid);
+	}
+
+}
-- 
2.13.6



More information about the eclipse-yocto mailing list