From 52483c01c2c7ff46199a034a890db62af3863c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20=27Morty=27=20Str=C3=BCbe?= Date: Tue, 30 Aug 2022 21:40:28 +0200 Subject: [PATCH] Docker: Allow setting a mapping for docker paths (#46) * Docker: Allow setting a mapping for docker paths Bug 579944 introduced using Windows paths, to allow putting your project into the WSL's file system. While Docker for Windows (the commercial version) supports Windows paths, this does not work with a OSS-docker installed in the WSL. It is now possible to provide a path mapping to work around this. Co-authored-by: Jeff Johnston --- .../META-INF/MANIFEST.MF | 2 +- .../launcher/ContainerCommandLauncher.java | 27 +- .../docker/launcher/ContainerLaunchUtils.java | 22 +- .../docker/launcher/ContainerPropertyTab.java | 233 ++++++++++++------ .../ContainerPropertyVolumesModel.java | 10 + .../internal/docker/launcher/Messages.java | 3 + .../docker/launcher/messages.properties | 3 + 7 files changed, 214 insertions(+), 86 deletions(-) diff --git a/launch/org.eclipse.cdt.docker.launcher/META-INF/MANIFEST.MF b/launch/org.eclipse.cdt.docker.launcher/META-INF/MANIFEST.MF index 2f27aba1f8d..c6eed08bace 100644 --- a/launch/org.eclipse.cdt.docker.launcher/META-INF/MANIFEST.MF +++ b/launch/org.eclipse.cdt.docker.launcher/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.cdt.docker.launcher;singleton:=true -Bundle-Version: 1.3.400.qualifier +Bundle-Version: 2.0.0.qualifier Bundle-Activator: org.eclipse.cdt.docker.launcher.DockerLaunchUIPlugin Bundle-Vendor: %Plugin.vendor Bundle-Localization: plugin diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/docker/launcher/ContainerCommandLauncher.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/docker/launcher/ContainerCommandLauncher.java index cae5dadc7e5..86dbfffaf7c 100644 --- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/docker/launcher/ContainerCommandLauncher.java +++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/docker/launcher/ContainerCommandLauncher.java @@ -62,6 +62,8 @@ public class ContainerCommandLauncher implements ICommandLauncher, ICBuildComman 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$ + /** @since 2.0 */ + public final static String DOCKERD_PATH = DockerLaunchUIPlugin.PLUGIN_ID + ".containerbuild.property.dockerdpath"; //$NON-NLS-1$ public final static String VOLUME_SEPARATOR_REGEX = "[|]"; //$NON-NLS-1$ @@ -253,14 +255,16 @@ public class ContainerCommandLauncher implements ICommandLauncher, ICBuildComman boolean keepContainer = prefs.getBoolean(PreferenceConstants.KEEP_CONTAINER_AFTER_LAUNCH, false); ICBuildConfiguration buildCfg = getBuildConfiguration(); - String selectedVolumeString = null; - String connectionName = null; - String imageName = null; + final String selectedVolumeString; + final String connectionName; + final String imageName; + final String pathMapProperty; if (buildCfg != null) { IToolChain toolChain = buildCfg.getToolChain(); selectedVolumeString = toolChain.getProperty(SELECTED_VOLUMES_ID); connectionName = toolChain.getProperty(IContainerLaunchTarget.ATTR_CONNECTION_URI); imageName = toolChain.getProperty(IContainerLaunchTarget.ATTR_IMAGE_ID); + pathMapProperty = toolChain.getProperty(DOCKERD_PATH); } else { ICConfigurationDescription cfgd = CoreModel.getDefault().getProjectDescription(fProject) .getActiveConfiguration(); @@ -272,6 +276,7 @@ public class ContainerCommandLauncher implements ICommandLauncher, ICBuildComman selectedVolumeString = props.getProperty(SELECTED_VOLUMES_ID); connectionName = props.getProperty(ContainerCommandLauncher.CONNECTION_ID); imageName = props.getProperty(ContainerCommandLauncher.IMAGE_ID); + pathMapProperty = props.getProperty(DOCKERD_PATH); } // Add any specified volumes to additional dir list @@ -288,8 +293,20 @@ public class ContainerCommandLauncher implements ICommandLauncher, ICBuildComman } setImageName(imageName); - additionalDirs.addAll( - additionalPaths.stream().map(p -> ContainerLaunchUtils.toDockerVolume(p)).collect(Collectors.toList())); + final Map pathMap = new HashMap<>(); + + if (pathMapProperty != null && !pathMapProperty.isEmpty()) { + final var entries = pathMapProperty.split(";"); //$NON-NLS-1$ + for (var e : entries) { + final var spl = e.split("\\|"); //$NON-NLS-1$ + if (spl.length == 2) { + pathMap.put(spl[0], spl[1]); + } + } + } + + additionalDirs.addAll(additionalPaths.stream().map(p -> ContainerLaunchUtils.toDockerVolume(pathMap, p)) + .collect(Collectors.toList())); fProcess = launcher.runCommand(connectionName, imageName, fProject, this, cmdList, workingDir, additionalDirs, origEnv, fEnvironment, supportStdin, privilegedMode, labels, keepContainer); diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchUtils.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchUtils.java index ff55ba042b9..db0f3ea1496 100644 --- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchUtils.java +++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerLaunchUtils.java @@ -13,6 +13,8 @@ *******************************************************************************/ package org.eclipse.cdt.internal.docker.launcher; +import java.util.Map; + import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; @@ -59,11 +61,23 @@ public class ContainerLaunchUtils { * @param path The path on the hose * @return The string to be passed to the docker daemon */ - public static final String toDockerVolume(IPath path) { - IPath p = path.makeAbsolute(); - String rv = toDockerPath(p); + public static final String toDockerVolume(Map pMap, IPath path) { + // The path on the Docker host + var dhPath = path.makeAbsolute().toString(); + + for (var me : pMap.entrySet()) { + var elp = me.getKey(); + var edhp = me.getValue(); + if (dhPath.startsWith(elp)) { + dhPath = edhp + dhPath.substring(elp.length()); + break; + } + } + + // docker-path first, docker-host-path third + String rv = toDockerPath(path.makeAbsolute()); rv += ":HOST_FILE_SYSTEM:"; //$NON-NLS-1$ - rv += p.toOSString(); + rv += dhPath; rv += ":false:true"; //$NON-NLS-1$ RO=false, selected = true return rv; } diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyTab.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyTab.java index 72bf3344fe5..7b6bcf99c01 100644 --- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyTab.java +++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyTab.java @@ -22,6 +22,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvider; import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvidersKeeper; @@ -86,6 +87,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; @SuppressWarnings("restriction") public class ContainerPropertyTab extends AbstractCBuildPropertyTab @@ -102,6 +104,7 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab private Button enableButton; private Button launchAutotoolsButton; private Button addButton; + private Text dockerDPath; private IDockerConnection connection; private IDockerConnection[] connections; private IDockerImageListener containerTab; @@ -170,83 +173,138 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab usercomp.setLayoutData(gd); - enableButton = new Button(usercomp, SWT.CHECK); - enableButton.setText(Messages.ContainerPropertyTab_Enable_Msg); + //Enable button + { + enableButton = new Button(usercomp, SWT.CHECK); + enableButton.setText(Messages.ContainerPropertyTab_Enable_Msg); - iCfg = getCfg(); - iCfgd = getResDesc().getConfiguration(); + iCfg = getCfg(); + iCfgd = getResDesc().getConfiguration(); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 5; - enableButton.setLayoutData(gd); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 5; + enableButton.setLayoutData(gd); + } + // Connection + { + Label connectionSelectorLabel = new Label(usercomp, SWT.NULL); + connectionSelectorLabel.setText(Messages.ContainerTab_Connection_Selector_Label); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + connectionSelectorLabel.setLayoutData(gd); - Label connectionSelectorLabel = new Label(usercomp, SWT.NULL); - connectionSelectorLabel.setText(Messages.ContainerTab_Connection_Selector_Label); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 1; - gd.grabExcessHorizontalSpace = false; - connectionSelectorLabel.setLayoutData(gd); + connectionSelector = new Combo(usercomp, SWT.BORDER | SWT.READ_ONLY); + connectionSelector.setToolTipText(Messages.ContainerTab_Connection_Selector_Tooltip); + initializeConnectionSelector(); + connectionSelector.addModifyListener(connectionModifyListener); + // Following is a kludge so that on Linux the Combo is read-only but + // has a white background. + connectionSelector.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent e) { + e.doit = false; + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + gd.grabExcessHorizontalSpace = true; + connectionSelector.setLayoutData(gd); - connectionSelector = new Combo(usercomp, SWT.BORDER | SWT.READ_ONLY); - connectionSelector.setToolTipText(Messages.ContainerTab_Connection_Selector_Tooltip); - initializeConnectionSelector(); - connectionSelector.addModifyListener(connectionModifyListener); - // Following is a kludge so that on Linux the Combo is read-only but - // has a white background. - connectionSelector.addVerifyListener(new VerifyListener() { - @Override - public void verifyText(VerifyEvent e) { - e.doit = false; - } - }); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 3; - gd.grabExcessHorizontalSpace = true; - connectionSelector.setLayoutData(gd); + Label label1 = new Label(usercomp, SWT.NULL); + gd = new GridData(); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + label1.setLayoutData(gd); + } + // DockerDPath + { + Label label = new Label(usercomp, SWT.NULL); + label.setText(Messages.ContainerPropertyTab_dockerDPath); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 1; + label.setLayoutData(gd); - Label label1 = new Label(usercomp, SWT.NULL); - gd = new GridData(); - gd.horizontalSpan = 1; - gd.grabExcessHorizontalSpace = false; - label1.setLayoutData(gd); + dockerDPath = new Text(usercomp, SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + dockerDPath.setLayoutData(gd); + dockerDPath.setToolTipText(Messages.ContainerPropertyTab_dockerDPath_Tooltip); + dockerDPath.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + setDockerDPath(dockerDPath.getText()); - Label imageSelectorLabel = new Label(usercomp, SWT.NULL); - imageSelectorLabel.setText(Messages.ContainerTab_Image_Selector_Label); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 1; - connectionSelectorLabel.setLayoutData(gd); + } + }); - imageCombo = new Combo(usercomp, SWT.DROP_DOWN); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 3; - gd.grabExcessHorizontalSpace = true; - imageCombo.setLayoutData(gd); + label = new Label(usercomp, SWT.NULL); + gd = new GridData(); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + label.setLayoutData(gd); - Label label2 = new Label(usercomp, SWT.NULL); - gd = new GridData(); - gd.horizontalSpan = 1; - gd.grabExcessHorizontalSpace = false; - label2.setLayoutData(gd); + label = new Label(usercomp, SWT.NULL); + gd = new GridData(); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + label.setLayoutData(gd); - initializeImageCombo(); + label = new Label(usercomp, SWT.NULL); + label.setText(Messages.ContainerPropertyTab_dockerDPath_Instruction); + gd = new GridData(); + gd.horizontalSpan = 3; + gd.grabExcessHorizontalSpace = false; + label.setLayoutData(gd); - imageCombo.addSelectionListener(new SelectionListener() { + label = new Label(usercomp, SWT.NULL); + gd = new GridData(); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + label.setLayoutData(gd); - @Override - public void widgetSelected(SelectionEvent e) { - setImageId(imageCombo.getText()); - model.setSelectedImage(displayedImages.get(imageCombo.getSelectionIndex())); - } + } + // Image selector + { + Label imageSelectorLabel = new Label(usercomp, SWT.NULL); + imageSelectorLabel.setText(Messages.ContainerTab_Image_Selector_Label); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 1; + imageSelectorLabel.setLayoutData(gd); - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } + imageCombo = new Combo(usercomp, SWT.DROP_DOWN); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + gd.grabExcessHorizontalSpace = true; + imageCombo.setLayoutData(gd); - }); + Label label2 = new Label(usercomp, SWT.NULL); + gd = new GridData(); + gd.horizontalSpan = 1; + gd.grabExcessHorizontalSpace = false; + label2.setLayoutData(gd); + initializeImageCombo(); + imageCombo.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + setImageId(imageCombo.getText()); + model.setSelectedImage(displayedImages.get(imageCombo.getSelectionIndex())); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + }); + } + // Volume createVolumeSettingsContainer(usercomp); + // Autotools try { + IProject project = iCfgd.getProjectDescription().getProject(); IProjectNature nature = project.getNature("org.eclipse.cdt.autotools.core.autotoolsNatureV2"); //$NON-NLS-1$ isAutotoolsProject = (nature != null); @@ -275,6 +333,7 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab DockerLaunchUIPlugin.log(e); } + // Handle enablement stuff initializeEnablementButton(); enableButton.addSelectionListener(new SelectionListener() { @@ -587,6 +646,20 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab } } + private void setDockerDPath(String ddPath) { + if (iCfg instanceof IMultiConfiguration) { + IConfiguration[] cfs = (IConfiguration[]) ((IMultiConfiguration) iCfg).getItems(); + for (int i = 0; i < cfs.length; i++) { + IConfiguration cfg = cfs[i]; + IOptionalBuildProperties p = cfg.getOptionalBuildProperties(); + p.setProperty(ContainerCommandLauncher.DOCKERD_PATH, ddPath); + } + } else { + IOptionalBuildProperties p = iCfg.getOptionalBuildProperties(); + p.setProperty(ContainerCommandLauncher.DOCKERD_PATH, ddPath); + } + } + private void setImageId(String imageId) { if (iCfg instanceof IMultiConfiguration) { IConfiguration[] cfs = (IConfiguration[]) ((IMultiConfiguration) iCfg).getItems(); @@ -715,6 +788,12 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab } + private void initializeDockerDPath() { + IOptionalBuildProperties properties = iCfg.getOptionalBuildProperties(); + var path = properties.getProperty(ContainerCommandLauncher.DOCKERD_PATH); + dockerDPath.setText(path == null ? "" : path); //$NON-NLS-1$ + } + private void initializeImageCombo() { initialImageId = null; IOptionalBuildProperties properties = iCfg.getOptionalBuildProperties(); @@ -878,22 +957,8 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab @Override protected void performDefaults() { - if (iCfg instanceof IMultiConfiguration) { - IConfiguration[] cfs = (IConfiguration[]) ((IMultiConfiguration) iCfg).getItems(); - for (int i = 0; i < cfs.length; i++) { - IOptionalBuildProperties props = cfs[i].getOptionalBuildProperties(); - props.setProperty(ContainerCommandLauncher.CONTAINER_BUILD_ENABLED, Boolean.toString(false)); - if (connections.length > 0) { - props.setProperty(ContainerCommandLauncher.CONNECTION_ID, connections[0].getUri()); - } else { - props.setProperty(ContainerCommandLauncher.CONNECTION_ID, null); - } - props.setProperty(ContainerCommandLauncher.IMAGE_ID, null); - props.setProperty(ContainerCommandLauncher.VOLUMES_ID, null); - props.setProperty(ContainerCommandLauncher.SELECTED_VOLUMES_ID, null); - } - } else { - IOptionalBuildProperties props = iCfg.getOptionalBuildProperties(); + + Consumer setProps = (IOptionalBuildProperties props) -> { props.setProperty(ContainerCommandLauncher.CONTAINER_BUILD_ENABLED, Boolean.toString(false)); if (connections.length > 0) { props.setProperty(ContainerCommandLauncher.CONNECTION_ID, connections[0].getUri()); @@ -901,6 +966,20 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab props.setProperty(ContainerCommandLauncher.CONNECTION_ID, null); } props.setProperty(ContainerCommandLauncher.IMAGE_ID, null); + props.setProperty(ContainerCommandLauncher.VOLUMES_ID, null); + props.setProperty(ContainerCommandLauncher.SELECTED_VOLUMES_ID, null); + props.setProperty(ContainerCommandLauncher.DOCKERD_PATH, null); + }; + + if (iCfg instanceof IMultiConfiguration) { + IConfiguration[] cfs = (IConfiguration[]) ((IMultiConfiguration) iCfg).getItems(); + for (int i = 0; i < cfs.length; i++) { + IOptionalBuildProperties props = cfs[i].getOptionalBuildProperties(); + setProps.accept(props); + } + } else { + IOptionalBuildProperties props = iCfg.getOptionalBuildProperties(); + setProps.accept(props); } initialEnabled = false; initialConnection = null; @@ -911,6 +990,7 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab connectionSelector.select(0); } imageCombo.setText(""); //$NON-NLS-1$ + dockerDPath.setText(""); //$NON-NLS-1$ model.setDataVolumes(null); model.setSelectedDataVolumes(new HashSet<>()); enableButton.setSelection(false); @@ -928,6 +1008,7 @@ public class ContainerPropertyTab extends AbstractCBuildPropertyTab initializeConnectionSelector(); initializeImageCombo(); + initializeDockerDPath(); initializeEnablementButton(); initializeVolumesTable(); } diff --git a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyVolumesModel.java b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyVolumesModel.java index b520dcdd557..9ce0b5f6a0b 100644 --- a/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyVolumesModel.java +++ b/launch/org.eclipse.cdt.docker.launcher/src/org/eclipse/cdt/internal/docker/launcher/ContainerPropertyVolumesModel.java @@ -53,6 +53,16 @@ public class ContainerPropertyVolumesModel extends BaseDatabindingModel { private IDockerImage selectedImage; + private String dockerDPath; + + public String getDockerDPath() { + return dockerDPath; + } + + public void setDockerDPath(String dockerDPath) { + this.dockerDPath = dockerDPath; + } + public ContainerPropertyVolumesModel(final IDockerConnection connection) { this.connection = connection; } 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 ebe386b5df4..ec95df07249 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 @@ -112,6 +112,9 @@ public class Messages extends NLS { public static String StandardGDBDebuggerPage14; + public static String ContainerPropertyTab_dockerDPath; + public static String ContainerPropertyTab_dockerDPath_Instruction; + public static String ContainerPropertyTab_dockerDPath_Tooltip; public static String ContainerPropertyTab_Title; public static String ContainerPropertyTab_Enable_Msg; public static String ContainerPropertyTab_Run_Autotools_In_Container_Msg; 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 c2542845474..e9bc73acf70 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 @@ -61,6 +61,9 @@ ContainerPortDialog_containerLabel=Container port: ContainerPortDialog_hostAddressLabel=Host address: ContainerPortDialog_hostPortLabel=Host port: +ContainerPropertyTab_dockerDPath=Path mapping: +ContainerPropertyTab_dockerDPath_Instruction="|;[..]" - e.g "C:|/mnt/c;D:|/mnt/d" +ContainerPropertyTab_dockerDPath_Tooltip=Leave empty when in doubt. Mapping is done using string replacement. ContainerPropertyTab_Title=Container Settings ContainerPropertyTab_Enable_Msg=Build inside Docker Image ContainerPropertyTab_Run_Autotools_In_Container_Msg=Run all Autotools in Container