Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'launch/org.eclipse.cdt.docker.launcher')
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchConfigurationDelegate.java76
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPortDialog.java230
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTab.java247
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTabModel.java87
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ExposedPortModel.java246
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ILaunchConstants.java3
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/Messages.java19
-rw-r--r--launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/messages.properties15
8 files changed, 917 insertions, 6 deletions
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchConfigurationDelegate.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchConfigurationDelegate.java
index 73872226ca1..8a63dbdbb03 100644
--- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchConfigurationDelegate.java
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchConfigurationDelegate.java
@@ -15,7 +15,7 @@ import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -223,6 +223,39 @@ public class ContainerLaunchConfigurationDelegate extends GdbLaunchDelegate
}
additionalDirs = dirs;
}
+
+ List<String> ports = new ArrayList<>();
+ List<String> portInfos = configuration.getAttribute(
+ ILaunchConstants.ATTR_EXPOSED_PORTS,
+ Collections.emptyList());
+ for (String portInfo : portInfos) {
+ ExposedPortModel m = ExposedPortModel
+ .createPortModel(portInfo);
+ if (m.getSelected()) {
+ StringBuilder b1 = new StringBuilder();
+ if (m.getHostAddress() != null
+ && !m.getHostAddress().isEmpty()) {
+ b1.append(m.getHostAddress());
+ b1.append(":"); //$NON-NLS-1$
+ }
+ if (m.getHostPort() != null
+ && !m.getHostPort().isEmpty()) {
+ b1.append(m.getHostPort());
+ }
+ // regardless if we have a host port or not,
+ // we may need to add a separator so we can determine
+ // the case where we don't have a host port vs where we
+ // don't have a host address
+ if (b1.length() > 0) {
+ b1.append(":"); //$NON-NLS-1$
+ }
+ String containerPort = m.getContainerPort() + "/" //$NON-NLS-1$
+ + m.getPortType();
+ b1.append(containerPort);
+ ports.add(b1.toString());
+ }
+ }
+
String image = configuration.getAttribute(
ILaunchConstants.ATTR_IMAGE, (String) null);
String connectionUri = configuration.getAttribute(
@@ -240,14 +273,49 @@ public class ContainerLaunchConfigurationDelegate extends GdbLaunchDelegate
connectionUri,
image, command,
commandDir, workingDir, additionalDirs, origEnv,
- envMap, null, keepContainer, supportStdin,
+ envMap, ports.isEmpty() ? null : ports, keepContainer,
+ supportStdin,
privilegedMode, labels);
} else if (mode.equals(ILaunchManager.DEBUG_MODE)) {
String gdbserverPortNumber = configuration.getAttribute(
ILaunchConstants.ATTR_GDBSERVER_PORT,
ILaunchConstants.ATTR_GDBSERVER_PORT_DEFAULT);
- List<String> ports = Arrays
- .asList(gdbserverPortNumber + "/tcp"); //$NON-NLS-1$
+
+ List<String> ports = new ArrayList<>();
+ List<String> portInfos = configuration.getAttribute(
+ ILaunchConstants.ATTR_EXPOSED_PORTS, Collections.emptyList());
+ String gdbserverPort = gdbserverPortNumber + "/tcp"; //$NON-NLS-1$
+ boolean gdbserverPortSpecified = false;
+ for (String portInfo : portInfos) {
+ ExposedPortModel m = ExposedPortModel.createPortModel(portInfo);
+ if (m.getSelected()) {
+ StringBuilder b = new StringBuilder();
+ if (m.getHostAddress() != null && !m.getHostAddress().isEmpty()) {
+ b.append(m.getHostAddress());
+ b.append(":"); //$NON-NLS-1$
+ }
+ if (m.getHostPort() != null && !m.getHostPort().isEmpty()) {
+ b.append(m.getHostPort());
+ }
+ // regardless if we have a host port or not,
+ // we may need to add a separator so we can determine
+ // the case where we don't have a host port vs where we
+ // don't have a host address
+ if (b.length() > 0) {
+ b.append(":"); //$NON-NLS-1$
+ }
+ String containerPort = m.getContainerPort() + "/" + m.getPortType(); //$NON-NLS-1$
+ b.append(containerPort);
+ if (gdbserverPort.equals(containerPort)) {
+ gdbserverPortSpecified = true;
+ }
+ ports.add(b.toString());
+ }
+ }
+ // if user hasn't already specified gdbserver port, we need to add it by default
+ if (!gdbserverPortSpecified) {
+ ports.add(gdbserverPortNumber + "/tcp"); //$NON-NLS-1$
+ }
String gdbserverCommand = configuration.getAttribute(
ILaunchConstants.ATTR_GDBSERVER_COMMAND,
ILaunchConstants.ATTR_GDBSERVER_COMMAND_DEFAULT);
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPortDialog.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPortDialog.java
new file mode 100644
index 00000000000..d803707948f
--- /dev/null
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPortDialog.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2018 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+
+package org.eclipse.cdt.internal.docker.launcher;
+
+import org.eclipse.core.databinding.DataBindingContext;
+import org.eclipse.core.databinding.beans.BeanProperties;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.jface.databinding.swt.ISWTObservableValue;
+import org.eclipse.jface.databinding.swt.WidgetProperties;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * @author xcoulon
+ *
+ */
+public class ContainerPortDialog extends Dialog {
+
+ private static final String PORT_TYPE = "tcp"; //$NON-NLS-1$
+
+ private final ContainerPortDialogModel model;
+
+ private final DataBindingContext dbc = new DataBindingContext();
+
+ public ContainerPortDialog(final Shell parentShell) {
+ super(parentShell);
+ this.model = new ContainerPortDialogModel();
+ }
+
+ public ContainerPortDialog(final Shell parentShell,
+ final ExposedPortModel selectedContainerPort) {
+ super(parentShell);
+ this.model = new ContainerPortDialogModel(
+ selectedContainerPort.getContainerPort(),
+ selectedContainerPort.getHostAddress(),
+ selectedContainerPort.getHostPort());
+ }
+
+ @Override
+ protected void configureShell(final Shell shell) {
+ super.configureShell(shell);
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ shell.setText(Messages.ContainerPortDialog_shellTitle);
+ }
+
+ /**
+ * Disable the 'OK' button by default
+ */
+ @Override
+ protected Button createButton(Composite parent, int id, String label,
+ boolean defaultButton) {
+ final Button button = super.createButton(parent, id, label,
+ defaultButton);
+ if (id == IDialogConstants.OK_ID) {
+ button.setEnabled(false);
+ }
+ return button;
+ }
+
+ @Override
+ protected Point getInitialSize() {
+ return new Point(400, super.getInitialSize().y);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ final int COLUMNS = 2;
+ final Composite container = new Composite(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
+ .span(COLUMNS, 1).grab(true, true).applyTo(container);
+ GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(10, 10)
+ .applyTo(container);
+ final Label explanationLabel = new Label(container, SWT.NONE);
+ explanationLabel.setText(Messages.ContainerPortDialog_explanationLabel);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .span(COLUMNS, 1).grab(false, false).applyTo(explanationLabel);
+ final Label containerLabel = new Label(container, SWT.NONE);
+ containerLabel.setText(Messages.ContainerPortDialog_containerLabel);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(false, false).applyTo(containerLabel);
+ final Text containerPortText = new Text(container, SWT.BORDER);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(true, false).applyTo(containerPortText);
+ final Label hostAddressLabel = new Label(container, SWT.NONE);
+ hostAddressLabel.setText(Messages.ContainerPortDialog_hostAddressLabel);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(false, false).applyTo(hostAddressLabel);
+ final Text hostAddressText = new Text(container, SWT.BORDER);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(true, false).applyTo(hostAddressText);
+ final Label hostPortLabel = new Label(container, SWT.NONE);
+ hostPortLabel.setText(
+ Messages.ContainerPortDialog_hostPortLabel);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(false, false).applyTo(hostPortLabel);
+ final Text hostPortText = new Text(container, SWT.BORDER);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(true, false).applyTo(hostPortText);
+ // error message
+ final Label errorMessageLabel = new Label(container, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .span(COLUMNS, 1).grab(true, false).applyTo(errorMessageLabel);
+
+ // listening to changes
+ final ISWTObservableValue containerPortObservable = WidgetProperties
+ .text(SWT.Modify).observe(containerPortText);
+ dbc.bindValue(containerPortObservable,
+ BeanProperties
+ .value(ContainerPortDialogModel.class,
+ ContainerPortDialogModel.CONTAINER_PORT)
+ .observe(model));
+ final ISWTObservableValue hostAddressObservable = WidgetProperties
+ .text(SWT.Modify).observe(hostAddressText);
+ dbc.bindValue(hostAddressObservable,
+ BeanProperties
+ .value(ContainerPortDialogModel.class,
+ ContainerPortDialogModel.HOST_ADDRESS)
+ .observe(model));
+ final ISWTObservableValue hostPortObservable = WidgetProperties
+ .text(SWT.Modify).observe(hostPortText);
+ dbc.bindValue(hostPortObservable,
+ BeanProperties
+ .value(ContainerPortDialogModel.class,
+ ContainerPortDialogModel.HOST_PORT)
+ .observe(model));
+
+ containerPortObservable.addValueChangeListener(
+ onContainerPortSettingsChanged());
+ hostPortObservable.addValueChangeListener(
+ onContainerPortSettingsChanged());
+ hostAddressObservable.addValueChangeListener(
+ onContainerPortSettingsChanged());
+ return container;
+ }
+
+ private IValueChangeListener<?> onContainerPortSettingsChanged() {
+ return event -> validateInput();
+ }
+
+ private void validateInput() {
+ final String containerPort = model.getContainerPort();
+ if (containerPort == null || containerPort.isEmpty()) {
+ setOkButtonEnabled(false);
+ } else {
+ setOkButtonEnabled(true);
+ }
+ }
+
+ private void setOkButtonEnabled(final boolean enabled) {
+ getButton(IDialogConstants.OK_ID).setEnabled(enabled);
+ }
+
+ public ExposedPortModel getPort() {
+ return new ExposedPortModel(model.getContainerPort(), PORT_TYPE,
+ model.getHostAddress(), model.getHostPort());
+ }
+
+ class ContainerPortDialogModel extends BaseDatabindingModel {
+
+ public static final String CONTAINER_PORT = "containerPort"; //$NON-NLS-1$
+
+ public static final String HOST_ADDRESS = "hostAddress"; //$NON-NLS-1$
+
+ public static final String HOST_PORT = "hostPort"; //$NON-NLS-1$
+
+ private String containerPort;
+
+ private String hostAddress;
+
+ private String hostPort;
+
+ public ContainerPortDialogModel() {
+ }
+
+ public ContainerPortDialogModel(final String containerPort,
+ final String hostAddress, final String hostPort) {
+ this.containerPort = containerPort;
+ this.hostAddress = hostAddress;
+ this.hostPort = hostPort;
+ }
+
+ public String getContainerPort() {
+ return containerPort;
+ }
+
+ public void setContainerPort(final String containerPort) {
+ firePropertyChange(CONTAINER_PORT, this.containerPort,
+ this.containerPort = containerPort);
+ }
+
+ public String getHostAddress() {
+ return hostAddress;
+ }
+
+ public void setHostAddress(final String hostName) {
+ firePropertyChange(HOST_ADDRESS, this.hostAddress,
+ this.hostAddress = hostName);
+ }
+
+ public String getHostPort() {
+ return hostPort;
+ }
+
+ public void setHostPort(final String hostPort) {
+ firePropertyChange(HOST_PORT, this.hostPort,
+ this.hostPort = hostPort);
+ }
+ }
+
+}
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTab.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTab.java
index ab76710de35..27118558091 100644
--- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTab.java
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTab.java
@@ -12,13 +12,31 @@ package org.eclipse.cdt.internal.docker.launcher;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
import org.eclipse.cdt.docker.launcher.DockerLaunchUIPlugin;
+import org.eclipse.core.databinding.DataBindingContext;
+import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
+import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
+import org.eclipse.jface.databinding.viewers.ViewerSupport;
+import org.eclipse.jface.databinding.viewers.ViewersObservables;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerConnectionManagerListener;
@@ -47,6 +65,8 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
import org.osgi.service.prefs.Preferences;
public class ContainerTab extends AbstractLaunchConfigurationTab implements
@@ -65,12 +85,21 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
private Button newButton;
private Button removeButton;
+
+ private CheckboxTableViewer tableViewer;
+
private Button keepButton;
private Button stdinButton;
private Button privilegedButton;
private Combo imageCombo;
private Combo connectionSelector;
+ private ContainerTabModel model;
+
+ private static final int INDENT = 1;
+
+ private final DataBindingContext dbc = new DataBindingContext();
+
private ModifyListener connectionModifyListener = new ModifyListener() {
@Override
@@ -92,6 +121,7 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
public ContainerTab() {
super();
containerTab = this;
+ model = new ContainerTabModel();
}
@Override
@@ -146,6 +176,7 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
createDirectoryList(mainComposite);
createButtons(mainComposite);
+ createPortSettingsSection(mainComposite);
createOptions(mainComposite);
}
@@ -214,6 +245,205 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
removeButton.setEnabled(false);
}
+ @SuppressWarnings("unchecked")
+ private void createPortSettingsSection(final Composite parent) {
+ Font font = parent.getFont();
+ Composite comp = createComposite(parent, 1, 2, GridData.FILL_BOTH);
+
+ Group group = new Group(comp, SWT.NONE);
+ group.setFont(font);
+ group.setText(Messages.ContainerTab_Ports_Group_Name);
+
+ GridData gd2 = new GridData(GridData.FILL_BOTH);
+ group.setLayoutData(gd2);
+
+ group.setLayout(new GridLayout());
+
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(3, 1)
+ .grab(true, false).applyTo(group);
+
+ group.setLayout(new GridLayout());
+ // specify ports
+ final Label portSettingsLabel = new Label(group, SWT.NONE);
+ portSettingsLabel.setText(Messages.ContainerTab_Specify_Ports_Label);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
+ .grab(true, false).span(3, 1)
+ .applyTo(portSettingsLabel);
+ final CheckboxTableViewer exposedPortsTableViewer = createPortSettingsTable(
+ group);
+ tableViewer = exposedPortsTableViewer;
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
+ .grab(true, false).span(3 - 1, 1).indent(INDENT, 0)
+ .hint(200, 70).applyTo(exposedPortsTableViewer.getTable());
+ // buttons
+ final Composite buttonsContainers = new Composite(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
+ .grab(false, false).applyTo(buttonsContainers);
+ GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0)
+ .spacing(SWT.DEFAULT, 0).applyTo(buttonsContainers);
+
+ final Button addButton = new Button(buttonsContainers, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
+ .grab(true, false).applyTo(addButton);
+ addButton.setText(Messages.ContainerTab_Add_Button);
+ addButton.addSelectionListener(onAddPort(exposedPortsTableViewer));
+ final Button editButton = new Button(buttonsContainers, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
+ .grab(true, false).applyTo(editButton);
+ editButton.setText(Messages.ContainerTab_Edit_Button);
+ editButton.setEnabled(false);
+ editButton.addSelectionListener(onEditPort(exposedPortsTableViewer));
+ final Button removeButton = new Button(buttonsContainers, SWT.NONE);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
+ .grab(true, false).applyTo(removeButton);
+ removeButton.setText(Messages.ContainerTab_Remove_Button);
+ removeButton
+ .addSelectionListener(onRemovePorts(exposedPortsTableViewer));
+ ViewerSupport.bind(exposedPortsTableViewer, model.getExposedPorts(),
+ BeanProperties.values(ExposedPortModel.class,
+ ExposedPortModel.CONTAINER_PORT,
+ ExposedPortModel.PORT_TYPE,
+ ExposedPortModel.HOST_ADDRESS,
+ ExposedPortModel.HOST_PORT));
+ dbc.bindSet(
+ ViewersObservables.observeCheckedElements(
+ exposedPortsTableViewer, ExposedPortModel.class),
+ BeanProperties.set(ContainerTabModel.SELECTED_PORTS)
+ .observe(model));
+ checkAllElements(exposedPortsTableViewer);
+
+ // disable the edit and removeButton if the table is empty
+ exposedPortsTableViewer.addSelectionChangedListener(
+ onSelectionChanged(editButton, removeButton));
+ exposedPortsTableViewer
+ .addCheckStateListener(new ICheckStateListener() {
+ @Override
+ public void checkStateChanged(
+ CheckStateChangedEvent event) {
+ ExposedPortModel e = (ExposedPortModel) event
+ .getElement();
+ e.setSelected(event.getChecked());
+ updateLaunchConfigurationDialog();
+ }
+ });
+ }
+
+ private void checkAllElements(
+ final CheckboxTableViewer exposedPortsTableViewer) {
+ exposedPortsTableViewer.setAllChecked(true);
+ model.setSelectedPorts(new HashSet<>(model.getExposedPorts()));
+ }
+
+ private SelectionListener onAddPort(
+ final CheckboxTableViewer exposedPortsTableViewer) {
+ return SelectionListener.widgetSelectedAdapter(e -> {
+ final ContainerPortDialog dialog = new ContainerPortDialog(
+ getShell());
+ dialog.create();
+ if (dialog.open() == IDialogConstants.OK_ID) {
+ final ExposedPortModel port = dialog.getPort();
+ port.setSelected(true);
+ model.addAvailablePort(port);
+ model.getSelectedPorts().add(port);
+ exposedPortsTableViewer.setChecked(port, true);
+ updateLaunchConfigurationDialog();
+ }
+ });
+ }
+
+ private SelectionListener onEditPort(
+ final CheckboxTableViewer exposedPortsTableViewer) {
+ return SelectionListener.widgetSelectedAdapter(e -> {
+ final IStructuredSelection selection = exposedPortsTableViewer
+ .getStructuredSelection();
+ final ExposedPortModel selectedContainerPort = (ExposedPortModel) selection
+ .getFirstElement();
+ final ContainerPortDialog dialog = new ContainerPortDialog(
+ getShell(), selectedContainerPort);
+ dialog.create();
+ if (dialog.open() == IDialogConstants.OK_ID) {
+ final ExposedPortModel configuredPort = dialog.getPort();
+ selectedContainerPort
+ .setContainerPort(configuredPort.getContainerPort());
+ selectedContainerPort
+ .setHostAddress(configuredPort.getHostAddress());
+ selectedContainerPort.setHostPort(configuredPort.getHostPort());
+ exposedPortsTableViewer.refresh();
+ updateLaunchConfigurationDialog();
+ }
+ });
+ }
+
+ private SelectionListener onRemovePorts(
+ final TableViewer portsTableViewer) {
+ return SelectionListener.widgetSelectedAdapter(e -> {
+ final IStructuredSelection selection = portsTableViewer
+ .getStructuredSelection();
+ for (@SuppressWarnings("unchecked")
+ Iterator<ExposedPortModel> iterator = selection.iterator(); iterator
+ .hasNext();) {
+ final ExposedPortModel port = iterator.next();
+ model.removeAvailablePort(port);
+ model.getSelectedPorts().remove(port);
+ updateLaunchConfigurationDialog();
+ }
+ });
+ }
+
+ private ISelectionChangedListener onSelectionChanged(
+ final Button... targetButtons) {
+ return e -> {
+ if (e.getSelection().isEmpty()) {
+ setControlsEnabled(targetButtons, false);
+ } else {
+ setControlsEnabled(targetButtons, true);
+ }
+ };
+ }
+
+ private static void setControlsEnabled(final Control[] controls,
+ final boolean enabled) {
+ for (Control control : controls) {
+ control.setEnabled(enabled);
+ }
+ }
+
+ private CheckboxTableViewer createPortSettingsTable(
+ final Composite container) {
+ final Table table = new Table(container, SWT.BORDER | SWT.FULL_SELECTION
+ | SWT.V_SCROLL | SWT.H_SCROLL | SWT.CHECK);
+ final CheckboxTableViewer tableViewer = new CheckboxTableViewer(table);
+ table.setHeaderVisible(true);
+ table.setLinesVisible(true);
+ createTableViewerColum(tableViewer,
+ Messages.ContainerTab_Port_Column,
+ 100);
+ createTableViewerColum(tableViewer,
+ Messages.ContainerTab_Type_Column,
+ 50);
+ createTableViewerColum(tableViewer,
+ Messages.ContainerTab_HostAddress_Column,
+ 100);
+ createTableViewerColum(tableViewer,
+ Messages.ContainerTab_HostPort_Column,
+ 100);
+ tableViewer.setContentProvider(new ObservableListContentProvider());
+ return tableViewer;
+ }
+
+ private TableViewerColumn createTableViewerColum(
+ final TableViewer tableViewer, final String title,
+ final int width) {
+ final TableViewerColumn viewerColumn = new TableViewerColumn(
+ tableViewer, SWT.NONE);
+ final TableColumn column = viewerColumn.getColumn();
+ if (title != null) {
+ column.setText(title);
+ }
+ column.setWidth(width);
+ return viewerColumn;
+ }
+
private void createOptions(Composite parent) {
Font font = parent.getFont();
Composite comp = createComposite(parent, 1, 3, GridData.FILL_BOTH);
@@ -406,6 +636,8 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(ILaunchConstants.ATTR_ADDITIONAL_DIRS,
(String) null);
+ configuration.setAttribute(ILaunchConstants.ATTR_EXPOSED_PORTS,
+ (String) null);
configuration.setAttribute(ILaunchConstants.ATTR_CONNECTION_URI, ""); //$NON-NLS-1$
Preferences prefs = InstanceScope.INSTANCE
.getNode(DockerLaunchUIPlugin.PLUGIN_ID);
@@ -427,6 +659,19 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
if (additionalDirs != null)
directoriesList.setItems(additionalDirs.toArray(new String[0]));
+
+ java.util.List<String> exposedPortInfos = configuration
+ .getAttribute(ILaunchConstants.ATTR_EXPOSED_PORTS,
+ Collections.<String> emptyList());
+ model.removeExposedPorts();
+ for (String port : exposedPortInfos) {
+ ExposedPortModel m = ExposedPortModel.createPortModel(port);
+ model.addAvailablePort(m);
+ if (m.getSelected()) {
+ model.getSelectedPorts().add(m);
+ tableViewer.setChecked(m, true);
+ }
+ }
connectionUri = configuration.getAttribute(
ILaunchConstants.ATTR_CONNECTION_URI, (String) "");
int defaultIndex = 0;
@@ -480,6 +725,8 @@ public class ContainerTab extends AbstractLaunchConfigurationTab implements
stdinButton.getSelection());
configuration.setAttribute(ILaunchConstants.ATTR_PRIVILEGED_MODE,
privilegedButton.getSelection());
+ configuration.setAttribute(ILaunchConstants.ATTR_EXPOSED_PORTS,
+ ExposedPortModel.toArrayString(model.getExposedPorts()));
}
@Override
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTabModel.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTabModel.java
new file mode 100644
index 00000000000..6fe7e02b98f
--- /dev/null
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerTabModel.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Red Hat.
+ * 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
+ *******************************************************************************/
+package org.eclipse.cdt.internal.docker.launcher;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.list.WritableList;
+
+/**
+ * @since 1.2.1
+ * @author jjohnstn
+ *
+ */
+public class ContainerTabModel extends BaseDatabindingModel {
+
+ public static final String PUBLISH_ALL_PORTS = "publishAllPorts"; //$NON-NLS-1$
+
+ public static final String EXPOSED_PORTS = "exposedPorts"; //$NON-NLS-1$
+
+ public static final String SELECTED_PORTS = "selectedPorts"; //$NON-NLS-1$
+
+ private boolean publishAllPorts = true;
+
+ private final WritableList<ExposedPortModel> exposedPorts = new WritableList<>();
+
+ private Set<ExposedPortModel> selectedPorts;
+
+ public boolean isPublishAllPorts() {
+ return publishAllPorts;
+ }
+
+ public void setPublishAllPorts(boolean publishAllPorts) {
+ firePropertyChange(PUBLISH_ALL_PORTS, this.publishAllPorts,
+ this.publishAllPorts = publishAllPorts);
+ }
+
+ public WritableList<ExposedPortModel> getExposedPorts() {
+ return exposedPorts;
+ }
+
+ public void addAvailablePort(final ExposedPortModel port) {
+ this.exposedPorts.add(port);
+ }
+
+ public void removeAvailablePort(final ExposedPortModel port) {
+ this.exposedPorts.remove(port);
+ }
+
+ public void setExposedPorts(final List<ExposedPortModel> exposedPorts) {
+ this.exposedPorts.clear();
+ this.exposedPorts.addAll(exposedPorts);
+ // FIXME: also add all given exposedPorts to selectedExposedPorts ?
+ }
+
+ public void addExposedPort(final ExposedPortModel exposedPort) {
+ if (!this.exposedPorts.contains(exposedPort)) {
+ this.exposedPorts.add(exposedPort);
+ }
+ }
+
+ public void removeExposedPort(final ExposedPortModel exposedPort) {
+ this.exposedPorts.remove(exposedPort);
+ }
+
+ public void removeExposedPorts() {
+ this.exposedPorts.clear();
+ }
+
+ public Set<ExposedPortModel> getSelectedPorts() {
+ return this.selectedPorts;
+ }
+
+ public void setSelectedPorts(final Set<ExposedPortModel> ports) {
+ firePropertyChange(SELECTED_PORTS, this.selectedPorts,
+ this.selectedPorts = ports);
+ }
+
+}
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ExposedPortModel.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ExposedPortModel.java
new file mode 100644
index 00000000000..000e65fd227
--- /dev/null
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ExposedPortModel.java
@@ -0,0 +1,246 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Red Hat.
+ * 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
+ *******************************************************************************/
+package org.eclipse.cdt.internal.docker.launcher;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.eclipse.core.runtime.Assert;
+
+public class ExposedPortModel extends BaseDatabindingModel
+ implements Comparable<ExposedPortModel> {
+
+ private static final String SEPARATOR = ":"; //$NON-NLS-1$
+
+ private static final String CONTAINER_TYPE_SEPARATOR = "/"; //$NON-NLS-1$
+
+ public static final String SELECTED = "selected"; //$NON-NLS-1$
+
+ public static final String CONTAINER_PORT = "containerPort"; //$NON-NLS-1$
+
+ public static final String PORT_TYPE = "portType"; //$NON-NLS-1$
+
+ public static final String HOST_ADDRESS = "hostAddress"; //$NON-NLS-1$
+
+ public static final String HOST_PORT = "hostPort"; //$NON-NLS-1$
+
+ private final String id = UUID.randomUUID().toString();
+
+ private boolean selected;
+
+ private String containerPort;
+
+ private String portType;
+
+ private String hostAddress;
+
+ private String hostPort;
+
+ /**
+ * Parses and converts the {@link List} of the given {@link String} values
+ * into a {@link List} of {@link ExposedPortModel}
+ *
+ * @param exposedPortInfos
+ * the input values
+ * @return the corresponding {@link ExposedPortModel}s
+ */
+ public static List<ExposedPortModel> fromStrings(
+ final Collection<String> exposedPortInfos) {
+ final List<ExposedPortModel> exposedPorts = new ArrayList<>();
+ for (String exposedPortInfo : exposedPortInfos) {
+ final ExposedPortModel exposedPort = ExposedPortModel
+ .fromString(exposedPortInfo);
+ if (exposedPort != null) {
+ exposedPorts.add(exposedPort);
+ }
+ }
+ return exposedPorts;
+ }
+
+ /**
+ * Converts a collection of ExposedPortModel to a {@link List} of
+ * {@link String} values
+ *
+ *
+ * @param exposedPorts
+ * collection of ExposedPortModel instances
+ * @return the corresponding {@link List} of {@link String}s
+ */
+ public static List<String> toArrayString(
+ final Collection<ExposedPortModel> exposedPorts) {
+ final List<String> exposedPortList = new ArrayList<>();
+ for (ExposedPortModel exposedPort : exposedPorts) {
+ final String exposedPortString = exposedPort.toString();
+ if (exposedPort != null) {
+ exposedPortList.add(exposedPortString);
+ }
+ }
+ return exposedPortList;
+ }
+
+ /**
+ * Parse the given value and returns an instance of
+ * {@link ExposedPortModel}.
+ *
+ * @param exposedPortInfo
+ * the value to parse
+ * @return the corresponding {@link ExposedPortModel}
+ */
+ public static ExposedPortModel fromString(final String exposedPortInfo) {
+ final String privatePort = exposedPortInfo.substring(0,
+ exposedPortInfo.indexOf(CONTAINER_TYPE_SEPARATOR));
+ // exposed ports without host IP/port info
+ final int firstColumnSeparator = exposedPortInfo.indexOf(SEPARATOR);
+ if (firstColumnSeparator == -1
+ && exposedPortInfo.indexOf(CONTAINER_TYPE_SEPARATOR) != -1) {
+ final String type = exposedPortInfo.substring(
+ exposedPortInfo.indexOf(CONTAINER_TYPE_SEPARATOR)); // $NON-NLS-1$
+ final ExposedPortModel exposedPort = new ExposedPortModel(
+ privatePort, type, "", privatePort); // $NON-NLS-1$
+ return exposedPort; // $NON-NLS-1$
+ } else {
+ final int secondColumnSeparator = exposedPortInfo.indexOf(SEPARATOR,
+ firstColumnSeparator + 1);
+ final String type = exposedPortInfo.substring(
+ exposedPortInfo.indexOf(CONTAINER_TYPE_SEPARATOR), // $NON-NLS-1$
+ firstColumnSeparator); // $NON-NLS-1$
+ final String hostIP = exposedPortInfo
+ .substring(firstColumnSeparator + 1, secondColumnSeparator);
+ final String hostPort = exposedPortInfo
+ .substring(secondColumnSeparator + 1);
+ final ExposedPortModel exposedPort = new ExposedPortModel(
+ privatePort, type, hostIP, hostPort); // $NON-NLS-1$
+ return exposedPort; // $NON-NLS-1$
+ }
+ }
+
+ /**
+ * Full constructor
+ *
+ * @param privatePort
+ * @param portType
+ * @param hostAddress
+ * @param hostPort
+ */
+ public ExposedPortModel(final String privatePort, final String type,
+ final String hostAddress, final String hostPort) {
+ Assert.isNotNull(privatePort,
+ "Port Mapping privatePort cannot be null"); //$NON-NLS-1$
+ Assert.isNotNull(type, "Port Mapping portType cannot be null"); //$NON-NLS-1$
+ this.containerPort = privatePort;
+ this.hostPort = hostPort;
+ this.portType = type;
+ this.hostAddress = hostAddress;
+ }
+
+ /**
+ * Create an ExposedPortModel from its toString output
+ *
+ * @param stringValue
+ * @return ExposedPortModel
+ */
+ static public ExposedPortModel createPortModel(String stringValue) {
+ final String[] elements = stringValue.split(SEPARATOR);
+ final String[] containerPortElements = elements[0]
+ .split(CONTAINER_TYPE_SEPARATOR);
+ ExposedPortModel model = new ExposedPortModel(containerPortElements[0],
+ containerPortElements[1], elements[1], elements[2]);
+ // check the last argument if exists otherwise assume 'true'
+ model.selected = (elements.length == 4) ? Boolean.valueOf(elements[3])
+ : true;
+ return model;
+ }
+
+ public String getContainerPort() {
+ return containerPort;
+ }
+
+ public void setContainerPort(final String containerPort) {
+ firePropertyChange(CONTAINER_PORT, this.containerPort,
+ this.containerPort = containerPort);
+ }
+
+ public String getPortType() {
+ return portType;
+ }
+
+ public void setPortType(final String type) {
+ firePropertyChange(PORT_TYPE, this.portType, this.portType = type);
+ }
+
+ public boolean getSelected() {
+ return selected;
+ }
+
+ public void setSelected(final boolean selected) {
+ firePropertyChange(SELECTED, this.selected, this.selected = selected);
+ }
+
+ public String getHostPort() {
+ return hostPort;
+ }
+
+ public void setHostPort(final String hostPort) {
+ firePropertyChange(HOST_PORT, this.hostPort, this.hostPort = hostPort);
+ }
+
+ public String getHostAddress() {
+ return hostAddress;
+ }
+
+ public void setHostAddress(final String hostAddress) {
+ firePropertyChange(HOST_ADDRESS, this.hostAddress,
+ this.hostAddress = hostAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ExposedPortModel other = (ExposedPortModel) obj;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ return true;
+ }
+
+ @Override
+ public int compareTo(final ExposedPortModel other) {
+ return this.containerPort.compareTo(other.containerPort);
+ }
+
+ // FIXME we should have a dedicated method to serialize the bean
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(containerPort + CONTAINER_TYPE_SEPARATOR + portType
+ + SEPARATOR + (hostAddress != null ? hostAddress : "")
+ + SEPARATOR + hostPort + SEPARATOR + selected);
+ return buffer.toString();
+ }
+
+}
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ILaunchConstants.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ILaunchConstants.java
index f430fc7825e..e4134f66043 100644
--- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ILaunchConstants.java
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ILaunchConstants.java
@@ -21,6 +21,9 @@ public interface ILaunchConstants {
public final static String ATTR_ADDITIONAL_DIRS = DockerLaunchUIPlugin
.getUniqueIdentifier() + ".additional_dirs"; //$NON-NLS-1$
+ public final static String ATTR_EXPOSED_PORTS = DockerLaunchUIPlugin
+ .getUniqueIdentifier() + ".exposed_ports"; //$NON-NLS-1$
+
public final static String ATTR_IMAGE = DockerLaunchUIPlugin.getUniqueIdentifier()
+ ".image"; //$NON-NLS-1$
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/Messages.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/Messages.java
index aa071ed6959..20efb046862 100644
--- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/Messages.java
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/Messages.java
@@ -15,8 +15,6 @@ import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "org.eclipse.cdt.internal.docker.launcher.messages"; //$NON-NLS-1$
-
-
public static String LaunchShortcut_Binaries;
public static String LaunchShortcut_Binary_not_found;
public static String LaunchShortcut_Choose_a_launch_configuration;
@@ -34,14 +32,25 @@ public class Messages extends NLS {
public static String ContainerTab_Name;
public static String ContainerTab_Group_Name;
public static String ContainerTab_Option_Group_Name;
+ public static String ContainerTab_Ports_Group_Name;
+ public static String ContainerTab_Specify_Ports_Label;
+
+ public static String ContainerTab_Add_Button;
+ public static String ContainerTab_Edit_Button;
public static String ContainerTab_New_Button;
public static String ContainerTab_Remove_Button;
public static String ContainerTab_Keep_Label;
+ public static String ContainerTab_Publish_All_Ports_Label;
public static String ContainerTab_Stdin_Support_Label;
public static String ContainerTab_Privileged_Mode_Label;
public static String ContainerTab_Error_Reading_Configuration;
public static String ContainerTab_Connection_Selector_Label;
public static String ContainerTab_Image_Selector_Label;
+ public static String ContainerTab_Port_Column;
+ public static String ContainerTab_Type_Column;
+ public static String ContainerTab_HostAddress_Column;
+ public static String ContainerTab_HostPort_Column;
+
public static String ContainerTab_Error_No_Connections;
public static String ContainerTab_Error_No_Images;
public static String ContainerTab_Warning_Connection_Not_Found;
@@ -113,6 +122,12 @@ public class Messages extends NLS {
public static String Gdbserver_Settings_Remotetimeout_tooltip;
+ public static String ContainerPortDialog_hostAddressLabel;
+ public static String ContainerPortDialog_hostPortLabel;
+ public static String ContainerPortDialog_shellTitle;
+ public static String ContainerPortDialog_containerLabel;
+ public static String ContainerPortDialog_explanationLabel;
+
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/messages.properties b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/messages.properties
index 5b8e30e9590..e54ac0ed21b 100644
--- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/messages.properties
+++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/messages.properties
@@ -26,20 +26,35 @@ Keep_Container_After_Launch=Keep Container after launch
ContainerTab_Name=Container
ContainerTab_New_Button=New...
+ContainerTab_Add_Button=Add...
+ContainerTab_Edit_Button=Edit...
ContainerTab_Remove_Button=Remove
ContainerTab_Keep_Label=Keep Container after launch
ContainerTab_Stdin_Support_Label=Support stdin input
ContainerTab_Privileged_Mode_Label=Run in privileged mode
ContainerTab_Group_Name=Required host directories
+ContainerTab_Ports_Group_Name=Ports
+ContainerTab_Specify_Ports_Label=Manually specify ports and only publish selected entries to the host:
+ContainerTab_Publish_All_Ports_Label=Publish all default exposed ports for image to random ports on the host
ContainerTab_Option_Group_Name=Additional Options
ContainerTab_Connection_Selector_Label=Connection:
ContainerTab_Image_Selector_Label=Image:
+ContainerTab_Port_Column=Container Port
+ContainerTab_Type_Column=Type
+ContainerTab_HostAddress_Column=Host Address
+ContainerTab_HostPort_Column=Host Port
ContainerTab_Error_Reading_Configuration=Error occurred reading the launch configuration: {0}
ContainerTab_Error_No_Connections=No Docker Connections exist
ContainerTab_Error_No_Images=No Docker Images exist
ContainerTab_Warning_Connection_Not_Found=Docker Connection: {0} for Launch Configuration not found: defaulting to {1}
ContainerTab_Warning_Image_Not_Found=Docker Image: {0} is not a valid pulled image in current Connection: {1}
+ContainerPortDialog_shellTitle=Exposing a Container Port
+ContainerPortDialog_explanationLabel=Specify the container port to expose:
+ContainerPortDialog_containerLabel=Container port:
+ContainerPortDialog_hostAddressLabel=Host address:
+ContainerPortDialog_hostPortLabel=Host port:
+
ContainerPropertyTab_Title=Container Settings
ContainerPropertyTab_Enable_Msg=Build inside Docker Image
ContainerPropertyTab_Run_Autotools_In_Container_Msg=Run all Autotools in Container

Back to the top