[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