Allow custom binaries in product exports.

Binaries can be provided per-platform, so it is only necessary to
provide where required. This allows to e.g. provide re-signed binaries
on windows whilst using the default mechanisms on all other platforms.

Change-Id: I4f7e7a97d9b4b93d29b841b194a2c41ca6a6bce1
Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com>
diff --git a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaApplicationLauncherAction.java b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaApplicationLauncherAction.java
index fb91ff1..7a0408f 100644
--- a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaApplicationLauncherAction.java
+++ b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaApplicationLauncherAction.java
@@ -16,6 +16,7 @@
 
 import org.eclipse.equinox.internal.p2.publisher.eclipse.ExecutablesDescriptor;
 import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.publisher.AbstractPublisherAction;
 import org.eclipse.equinox.p2.publisher.IPublisherAction;
 import org.eclipse.equinox.p2.publisher.eclipse.ApplicationLauncherAction;
 
@@ -25,24 +26,71 @@
 	private final String id;
 	private final Version version;
 	private final String flavor;
+	private final File platformLocation;
+	private final File overrideLocation;
 
 	public TeaApplicationLauncherAction(String id, Version version, String flavor, String executableName, File location,
-			String[] configSpecs) {
+			String[] configSpecs, File overrideLocation) {
 		super(id, version, flavor, executableName, location, configSpecs);
 		this.id = id;
 		this.version = version;
 		this.flavor = flavor;
+		this.platformLocation = location;
+		this.overrideLocation = overrideLocation;
 	}
 
 	@Override
 	protected Collection<IPublisherAction> createExecutablesActions(String[] configs) {
 		Collection<IPublisherAction> actions = new ArrayList<>(configs.length);
 		for (int i = 0; i < configs.length; i++) {
-			ExecutablesDescriptor executables = computeExecutables(configs[i]);
-			IPublisherAction action = new TeaEquinoxExecutableAction(executables, configs[i], id, version, flavor);
+			boolean hasOverridden = true;
+			ExecutablesDescriptor executables = createExecutablesFromLocation(overrideLocation, configs[i]);
+			if (executables == null) {
+				hasOverridden = false;
+				executables = createExecutablesFromLocation(new File(platformLocation, "bin"), configs[i]);
+
+				if (executables == null) {
+					// also no executables in the target, fall back to original
+					// code...
+					executables = computeExecutables(configs[i]);
+				}
+			}
+			IPublisherAction action = new TeaEquinoxExecutableAction(executables, configs[i], id, version, flavor,
+					!hasOverridden);
 			actions.add(action);
 		}
 		return actions;
 	}
 
+	public static ExecutablesDescriptor createExecutablesFromLocation(File executablesFeatureLocation,
+			String configSpec) {
+		if (executablesFeatureLocation == null || !executablesFeatureLocation.exists()) {
+			return null;
+		}
+		String[] config = AbstractPublisherAction.parseConfigSpec(configSpec);
+		File result = new File(executablesFeatureLocation, config[0] + "/" + config[1] + "/" + config[2]);
+		if (!result.exists() || !result.isDirectory()) {
+			return null;
+		}
+
+		String launcherName = null;
+		for (File file : result.listFiles()) {
+			if (file.getName().toLowerCase().equals("eclipsec.exe")) {
+				// ignore for compat.
+				continue;
+			}
+
+			if (file.getName().toLowerCase().endsWith(".exe")) {
+				launcherName = file.getName().substring(file.getName().length() - 4);
+			} else {
+				launcherName = file.getName();
+			}
+
+			break;
+		}
+
+		return new ExecutablesDescriptor(config[1], launcherName == null ? "launcher" : launcherName, result,
+				new File[] { result });
+	}
+
 }
diff --git a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaEquinoxExecutableAction.java b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaEquinoxExecutableAction.java
index 68f578e..a41b054 100644
--- a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaEquinoxExecutableAction.java
+++ b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaEquinoxExecutableAction.java
@@ -21,13 +21,19 @@
 @SuppressWarnings("restriction")
 public class TeaEquinoxExecutableAction extends EquinoxExecutableAction {
 
+	private final boolean fullBrand;
+
 	public TeaEquinoxExecutableAction(ExecutablesDescriptor executables, String configSpec, String idBase,
-			Version version, String flavor) {
+			Version version, String flavor, boolean fullBrand) {
 		super(executables, configSpec, idBase, version, flavor);
+
+		this.fullBrand = fullBrand;
 	}
 
 	@Override
 	protected void fullBrandExecutables(ExecutablesDescriptor descriptor, IBrandingAdvice advice) {
-		super.fullBrandExecutables(descriptor, new TeaBrandingAdvice(advice));
+		if (fullBrand) {
+			super.fullBrandExecutables(descriptor, new TeaBrandingAdvice(advice));
+		}
 	}
 }
diff --git a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaProductAction.java b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaProductAction.java
index 9cbeb84..7259e4d 100644
--- a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaProductAction.java
+++ b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/p2/TeaProductAction.java
@@ -56,6 +56,9 @@
 @SuppressWarnings("restriction")
 public class TeaProductAction extends ProductAction implements IPDEBuildConstants {
 
+	private final File platformExecutables;
+	private final File overrideExecutables;
+
 	/**
 	 * Creates a new product publishing action
 	 *
@@ -64,8 +67,11 @@
 	 * @param executables
 	 *            the location of the feature containing the executables
 	 */
-	public TeaProductAction(IProductDescriptor product, File executables) {
+	public TeaProductAction(IProductDescriptor product, File executables, File overrideExecutables) {
 		super(null, product, "tooling", executables);
+
+		this.platformExecutables = executables;
+		this.overrideExecutables = overrideExecutables;
 	}
 
 	@Override
@@ -147,8 +153,8 @@
 
 	@Override
 	protected IPublisherAction createApplicationExecutableAction(String[] configSpecs) {
-		return new TeaApplicationLauncherAction(id, version, flavor, executableName, getExecutablesLocation(),
-				configSpecs);
+		return new TeaApplicationLauncherAction(id, version, flavor, executableName, platformExecutables, configSpecs,
+				overrideExecutables);
 	}
 
 	/**
diff --git a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/tasks/p2/TaskPublishProductUpdateSite.java b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/tasks/p2/TaskPublishProductUpdateSite.java
index 993f896..18df499 100644
--- a/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/tasks/p2/TaskPublishProductUpdateSite.java
+++ b/org.eclipse.tea.library.build/src/org/eclipse/tea/library/build/tasks/p2/TaskPublishProductUpdateSite.java
@@ -123,6 +123,21 @@
 
 		final UpdateSite site = um.getSite(siteName);
 
+		// ensure the feature is existing
+		final FeatureBuild feature = wb.getFeature(productFeature);
+		if (feature == null) {
+			throw new RuntimeException("Cannot find feature " + productFeature + "'");
+		}
+
+		// check if there are binaries in the feature.
+		Properties props = readProperties(feature);
+		String customBin = props.getProperty("customBinaries");
+		boolean hasBin = false;
+
+		if (customBin != null) {
+			hasBin = true;
+		}
+
 		final Set<File> featureLocations = new LinkedHashSet<>();
 		final Set<File> pluginLocations = new LinkedHashSet<>();
 		File deltaPack = null;
@@ -160,19 +175,13 @@
 			}
 		}
 
-		if (deltaPack == null) {
+		if (deltaPack == null && !hasBin) {
 			throw new IllegalStateException(
 					"Cannot find delta-pack. Do you have the correct Target Platform activated?");
 		}
 
 		log.info("found " + (featureLocations.size() - fSize) + " locations from target platform");
 
-		// ensure the feature is existing
-		final FeatureBuild feature = wb.getFeature(productFeature);
-		if (feature == null) {
-			throw new RuntimeException("Cannot find feature " + productFeature + "'");
-		}
-
 		// ensure that the product file is existing
 		final File productFile = new File(feature.getData().getBundleDir(), productFileName);
 		if (!productFile.exists() || !productFile.isFile()) {
@@ -203,7 +212,8 @@
 		actions.add(new FeaturesAction(featureLocations.toArray(new File[featureLocations.size()])));
 		actions.add(new BundlesAction(pluginLocations.toArray(new File[pluginLocations.size()])));
 
-		actions.add(new TeaProductAction(productDescriptor, getExecutablesDir(deltaPack)));
+		actions.add(new TeaProductAction(productDescriptor, getExecutablesDir(deltaPack),
+				hasBin ? new File(feature.getData().getBundleDir(), customBin) : null));
 		actions.add(new RootIUAction(feature.getFeatureName(), Version.parseVersion(featureVersion),
 				feature.getFeatureName()));
 
@@ -274,14 +284,10 @@
 	 * content of the update site
 	 */
 	protected File createCategoryFile(JarManager jarManager, WorkspaceBuild wb, FeatureBuild feature) throws Exception {
-		File propertyFile = new File(feature.getData().getBundleDir(), "wpob.properties");
-		if (!propertyFile.exists() || !propertyFile.canRead()) {
-			throw new RuntimeException("Unable to find '" + propertyFile + "'");
-		}
-		Properties properties = FileUtils.readProperties(propertyFile);
+		Properties properties = readProperties(feature);
 		String categoryName = properties.getProperty("category");
 		if (categoryName == null || categoryName.isEmpty()) {
-			throw new RuntimeException("Missing 'category' entry in '" + propertyFile + "'");
+			throw new RuntimeException("Missing 'category' entry in 'wpob.properties'");
 		}
 		File dirName = BuildDirectories.get().getOutputDirectory();
 		File categoryFile = new File(dirName, "category.xml");
@@ -290,6 +296,15 @@
 		return categoryFile;
 	}
 
+	private Properties readProperties(FeatureBuild feature) {
+		File propertyFile = new File(feature.getData().getBundleDir(), "wpob.properties");
+		if (!propertyFile.exists() || !propertyFile.canRead()) {
+			throw new RuntimeException("Unable to find '" + propertyFile + "'");
+		}
+		Properties properties = FileUtils.readProperties(propertyFile);
+		return properties;
+	}
+
 	/**
 	 * Computes and returns the location of the feature containing the
 	 * executables