blob: 8ede389c38d3b4ace6dc11c33475d927b81c0724 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2012 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Code 9 - Additional function and fixes
*******************************************************************************/
package org.eclipse.equinox.internal.p2.publisher.eclipse;
import java.io.*;
import javax.xml.transform.TransformerException;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.pde.internal.publishing.Utils;
import org.eclipse.pde.internal.swt.tools.IconExe;
/**
*
*/
public class BrandingIron {
private static final String XDOC_ICON = "-Xdock:icon=../Resources/Eclipse.icns"; //$NON-NLS-1$
private static final String XDOC_ICON_PREFIX = "-Xdock:icon=../Resources/"; //$NON-NLS-1$
private String[] icons = null;
private String name;
private String applicationName;
private String description;
private String os = "win32"; //$NON-NLS-1$
private boolean brandIcons = true;
private String id;
private Version version;
public BrandingIron() {
}
public void setName(String value) {
name = value;
if (applicationName == null) {
applicationName = value;
}
}
/**
* Sets the name for the MacOS "<i>ProductName</i>.app" folder.
*/
public void setApplicationName(String value) {
applicationName = value;
}
public void setIcons(String[] value) {
icons = (value == null || value.length == 0) ? null : value;
}
public void setIcons(String value) {
icons = org.eclipse.equinox.internal.frameworkadmin.utils.Utils.getTokens(value, ",");//$NON-NLS-1$
if (icons[0].startsWith("${")) { //$NON-NLS-1$
if (icons.length > 1) {
String[] temp = new String[icons.length - 1];
System.arraycopy(icons, 1, temp, 0, temp.length);
icons = temp;
} else {
icons = null;
}
}
//trim whitespace
if (icons != null) {
for (int i = 0; i < icons.length; i++) {
icons[i] = icons[i].trim();
}
}
}
public void brand(ExecutablesDescriptor descriptor) throws Exception {
// if the name property is not set it will be ${launcher.name} so just bail.
if (name.startsWith("${")) //$NON-NLS-1$
return;
if (icons == null || icons[0].startsWith("${")) //$NON-NLS-1$
brandIcons = false;
File root = descriptor.getLocation();
// if the root does not exists (happens in some packaging cases) or
// there is already a file with target name and we don't need to update its icons, don't do anything
String testName = os.equals("win32") ? name + ".exe" : name; //$NON-NLS-1$ //$NON-NLS-2$
if (!root.exists() || (!brandIcons && new File(root, testName).exists()))
return;
// make sure the descriptor's location is a canonical path otherwise
// removing files from it may fail (this happens notably on Windows)
try {
root = root.getCanonicalFile();
} catch (IOException e) {
// try to live with an absolute path
root = root.getAbsoluteFile();
}
descriptor.setLocation(root);
if (os==null)
renameLauncher(descriptor);
else switch (os) {
case "win32": //$NON-NLS-1$
brandWindows(descriptor);
break;
case "linux": //$NON-NLS-1$
brandLinux(descriptor);
break;
case "solaris": //$NON-NLS-1$
brandSolaris(descriptor);
break;
case "macosx": //$NON-NLS-1$
brandMac(descriptor);
break;
case "aix": //$NON-NLS-1$
brandAIX(descriptor);
break;
case "hpux": //$NON-NLS-1$
brandHPUX(descriptor);
break;
default:
renameLauncher(descriptor);
break;
}
descriptor.setExecutableName(name, true);
}
private void brandAIX(ExecutablesDescriptor descriptor) {
renameLauncher(descriptor);
}
private void brandHPUX(ExecutablesDescriptor descriptor) {
renameLauncher(descriptor);
}
private void brandLinux(ExecutablesDescriptor descriptor) throws Exception {
renameLauncher(descriptor);
if (brandIcons) {
File icon = null;
if (icons.length > 0) {
for (int i = 0; i < icons.length; i++) {
if (icons[i].toLowerCase().endsWith(".xpm")) { //$NON-NLS-1$
icon = new File(icons[i]);
break;
}
}
}
if (icon != null) {
File targetIcon = new File(descriptor.getLocation(), "icon.xpm"); //$NON-NLS-1$
Utils.copy(icon, targetIcon);
descriptor.addFile(targetIcon);
}
}
}
private void brandSolaris(ExecutablesDescriptor descriptor) throws Exception {
renameLauncher(descriptor);
if (brandIcons == false)
return;
File root = descriptor.getLocation();
for (int i = 0; i < icons.length; i++) {
String icon = icons[i];
int iconNameLength = icon.length();
if (iconNameLength < 5)
continue;
String extension = icon.substring(iconNameLength - 5);
// check if the extension is one of: .l.pm, .m.pm, .s.pm, .t.pm
if (extension.charAt(0) == '.' && extension.endsWith(".pm") && "lmst".indexOf(extension.charAt(1)) >= 0) { //$NON-NLS-1$ //$NON-NLS-2$
File targetIcon = new File(root, name + extension);
Utils.copy(new File(icon), targetIcon);
descriptor.addFile(targetIcon);
}
}
}
private void brandMac(ExecutablesDescriptor descriptor) throws Exception {
//Initially the files are in: <root>/Eclipse.app/
//and they must appear in <root>/MyAppName.app/
//Because java does not support the rename of a folder, files are copied.
//Initialize the target folders
File root = descriptor.getLocation();
File target = root;
target.mkdirs();
new File(target, "MacOS").mkdirs(); //$NON-NLS-1$
new File(target, "Resources").mkdirs(); //$NON-NLS-1$
File initialRoot = new File(root, "Launcher.app/Contents"); //$NON-NLS-1$
if (!initialRoot.exists())
initialRoot = new File(root, "launcher.app/Contents"); //$NON-NLS-1$
if (!initialRoot.exists())
initialRoot = new File(root, "Eclipse.app/Contents"); //$NON-NLS-1$
if (!initialRoot.exists())
initialRoot = new File(root, "eclipse.app/Contents"); //$NON-NLS-1$
if (!initialRoot.exists())
throw new FileNotFoundException("cannot find launcher root (Eclipse.app or Launcher.app)"); //$NON-NLS-1$
// use the canonical rep to avoid possible issues from case-insensitive file systems
initialRoot = initialRoot.getCanonicalFile();
copyMacLauncher(descriptor, initialRoot, target);
String iconName = ""; //$NON-NLS-1$
if (brandIcons) {
File icon = null;
for (int i = 0; i < icons.length; i++) {
if (icons[i].toLowerCase().endsWith(".icns")) { //$NON-NLS-1$
icon = new File(icons[i]);
if (icon.exists()) {
break;
}
}
}
if (icon != null) {
iconName = icon.getName(); // was previously: name + ".icns"
File initialIcon = new File(initialRoot, "Resources/Eclipse.icns"); //$NON-NLS-1$
try {
// canonicalize to ensure case matches
initialIcon = initialIcon.getCanonicalFile();
} catch (IOException e) { /*ignore*/
}
File targetIcon = new File(target, "Resources/" + iconName); //$NON-NLS-1$
initialIcon.delete();
descriptor.removeFile(initialIcon);
Utils.copy(icon, targetIcon);
descriptor.addFile(targetIcon);
}
}
copyMacIni(descriptor, initialRoot, target, iconName);
modifyInfoPListFile(descriptor, initialRoot, target, iconName);
File splashApp = new File(initialRoot, "Resources/Splash.app"); //$NON-NLS-1$
if (splashApp.exists()) {
brandMacSplash(descriptor, initialRoot, target, iconName);
}
// move over any files remaining in the original folder
moveContents(descriptor, initialRoot, target);
initialRoot.getParentFile().delete(); // initialRoot is xxx.app/Contents
}
/**
* Brand the splash.app Info.plist and link or copy the mac launcher.
* It is assumed that the mac launcher has been branded already.
* @param descriptor
* @param initialRoot
* @param target
* @param iconName
*/
private void brandMacSplash(ExecutablesDescriptor descriptor, File initialRoot, File target, String iconName) {
String splashContents = "Resources/Splash.app/Contents"; //$NON-NLS-1$
modifyInfoPListFile(descriptor, new File(initialRoot, splashContents), new File(target, splashContents), iconName);
//link the MacOS launcher for the splash app
String splashMacOS = splashContents + "/MacOS"; //$NON-NLS-1$
File macOSDir = new File(target, "MacOS"); //$NON-NLS-1$
File splashMacOSDir = new File(target, splashMacOS);
splashMacOSDir.mkdirs();
File targetLauncher = new File(splashMacOSDir, name);
int result = -1;
String osName = System.getProperty("os.name"); //$NON-NLS-1$
if (osName != null && !osName.startsWith("Windows")) { //$NON-NLS-1$
try {
String[] command = new String[] {"ln", "-sf", "../../../MacOS/" + name, name}; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
Process proc = Runtime.getRuntime().exec(command, null, splashMacOSDir);
result = proc.waitFor();
} catch (IOException e) {
// ignore
} catch (InterruptedException e) {
// ignore
}
}
if (result != 0) {
//ln failed, or we are on windows, just copy the executable instead
try {
Utils.copy(new File(macOSDir, name), targetLauncher);
try {
Runtime.getRuntime().exec(new String[] {"chmod", "755", targetLauncher.getAbsolutePath()}); //$NON-NLS-1$ //$NON-NLS-2$
} catch (IOException e) {
// ignore
}
} catch (IOException e) {
System.out.println("Could not copy macosx splash launcher"); //$NON-NLS-1$
}
}
File initialLauncher = findLauncher(new File(initialRoot, splashMacOS));
if (initialLauncher != null) {
try {
if (!initialLauncher.getCanonicalFile().equals(targetLauncher.getCanonicalFile()))
initialLauncher.delete();
} catch (IOException e) {
// ignore
}
descriptor.removeFile(initialLauncher);
}
descriptor.addFile(targetLauncher);
}
private File findLauncher(File root) {
for (String launcherName : new String[] {"launcher", "eclipse"}) { //$NON-NLS-1$ //$NON-NLS-2$
File launcher = new File(root, launcherName);
if (launcher.exists())
return launcher;
}
return null;
}
private void moveContents(ExecutablesDescriptor descriptor, File source, File target) {
if (!source.exists())
return;
try {
source = source.getCanonicalFile();
target = target.getCanonicalFile();
} catch (IOException e) {
System.out.println("Could not copy macosx resources."); //$NON-NLS-1$
return;
}
if (source.equals(target))
return;
target.getParentFile().mkdirs();
if (source.isDirectory()) {
target.mkdirs();
File[] contents = source.listFiles();
for (int i = 0; i < contents.length; i++) {
File dest = new File(target, contents[i].getName());
if (contents[i].isFile()) {
contents[i].renameTo(dest);
descriptor.replace(contents[i], dest);
} else
moveContents(descriptor, contents[i], dest);
}
source.delete();
} else {
source.renameTo(target);
descriptor.replace(source, target);
}
}
private void brandWindows(ExecutablesDescriptor descriptor) throws Exception {
File root = descriptor.getLocation();
File templateLauncher = new File(root, name + ".exe"); //$NON-NLS-1$
if (!templateLauncher.exists())
templateLauncher = new File(root, "launcher.exe"); //$NON-NLS-1$
if (!templateLauncher.exists())
templateLauncher = new File(root, "eclipse.exe"); //$NON-NLS-1$
if (brandIcons) {
if (templateLauncher.exists()) {
String[] args = new String[icons.length + 1];
args[0] = templateLauncher.getAbsolutePath();
System.arraycopy(icons, 0, args, 1, icons.length);
IconExe.main(args);
} else {
System.out.println("Could not find executable to brand"); //$NON-NLS-1$
}
}
File targetLauncher = new File(root, name + ".exe"); //$NON-NLS-1$
if (templateLauncher.exists() && !templateLauncher.getName().equals(name + ".exe")) { //$NON-NLS-1$
templateLauncher.renameTo(targetLauncher);
descriptor.replace(templateLauncher, targetLauncher);
}
descriptor.setExecutableName(name, true);
}
private void renameLauncher(ExecutablesDescriptor descriptor) {
File root = descriptor.getLocation();
File launcher = findLauncher(root);
if (launcher == null)
return;
File targetLauncher = new File(root, name);
launcher.renameTo(targetLauncher);
descriptor.replace(launcher, targetLauncher);
descriptor.setExecutableName(name, true);
}
private void copyMacLauncher(ExecutablesDescriptor descriptor, File initialRoot, File target) {
File launcher = getCanonicalFile(new File(initialRoot, "MacOS/launcher")); //$NON-NLS-1$
File eclipseLauncher = getCanonicalFile(new File(initialRoot, "MacOS/eclipse")); //$NON-NLS-1$
File targetFile = getCanonicalFile(new File(target, "MacOS/" + name)); //$NON-NLS-1$
if (!launcher.exists()) {
launcher = eclipseLauncher;
} else if (eclipseLauncher.exists() && !targetFile.equals(eclipseLauncher)) {
//we may actually have both if exporting from the mac
eclipseLauncher.delete();
descriptor.removeFile(eclipseLauncher);
}
if (!targetFile.equals(launcher)) {
try {
Utils.copy(launcher, targetFile);
} catch (IOException e) {
System.out.println("Could not copy macosx launcher"); //$NON-NLS-1$
return;
}
launcher.delete();
launcher.getParentFile().delete();
descriptor.replace(launcher, targetFile);
}
try {
//Force the executable bit on the exe because it has been lost when copying the file
Runtime.getRuntime().exec(new String[] {"chmod", "755", targetFile.getAbsolutePath()}); //$NON-NLS-1$ //$NON-NLS-2$
} catch (IOException e) {
//ignore
}
descriptor.setExecutableName(name, false);
}
private File getCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
return file;
}
}
private void copyMacIni(ExecutablesDescriptor descriptor, File initialRoot, File target, String iconName) {
String brandedIniName = "MacOS/" + name + ".ini"; //$NON-NLS-1$//$NON-NLS-2$
// 4 possibilities, in order of preference:
// rcp.app/Contents/MacOS/rcp.ini (targetFile)
// Eclipse.app/Contents/MacOS/rcp.ini (brandedIni)
// Eclipse.app/Contents/MacOS/eclipse.ini (ini)
// Eclipse.app/Contents/MacOS/Eclipse.ini (ini2)
File targetFile = getCanonicalFile(new File(target, brandedIniName));
File brandedIni = getCanonicalFile(new File(initialRoot, brandedIniName));
File ini = getCanonicalFile(new File(initialRoot, "MacOS/eclipse.ini")); //$NON-NLS-1$
File ini2 = getCanonicalFile(new File(initialRoot, "MacOS/Eclipse.ini")); //$NON-NLS-1$
if (targetFile.exists()) {
//an ini already exists at the target, use that
if (brandedIni.exists() && !brandedIni.equals(targetFile)) {
brandedIni.delete();
descriptor.removeFile(brandedIni);
}
if (ini.exists() && !ini.equals(targetFile)) {
ini.delete();
descriptor.removeFile(ini);
}
if (ini2.exists() && !ini2.equals(targetFile)) {
ini2.delete();
descriptor.removeFile(ini2);
}
ini = targetFile;
} else if (brandedIni.exists()) {
//take the one that is already branded
if (ini.exists() && !ini.equals(brandedIni)) {
ini.delete();
descriptor.removeFile(ini);
}
if (ini2.exists() && !ini2.equals(brandedIni)) {
ini2.delete();
descriptor.removeFile(ini2);
}
ini = brandedIni;
} else {
if (ini.exists()) {
if (ini2.exists() && !ini2.equals(ini)) {
// this should not happen really
System.out.printf("Found both %s and %s - discarding the latter", ini.getAbsolutePath(), ini2.getAbsolutePath()); //$NON-NLS-1$
ini2.delete();
descriptor.removeFile(ini2);
}
} else if (ini2.exists()) {
ini = ini2;
} else
return;
}
StringBuffer buffer;
try {
buffer = readFile(ini);
} catch (IOException e) {
System.out.println("Impossible to brand ini file"); //$NON-NLS-1$
return;
}
if (iconName.length() > 0) {
int xdoc = scan(buffer, 0, XDOC_ICON);
if (xdoc != -1) {
String icns = XDOC_ICON_PREFIX + iconName;
buffer.replace(xdoc, xdoc + XDOC_ICON.length(), icns);
}
}
try {
transferStreams(new ByteArrayInputStream(buffer.toString().getBytes()), new FileOutputStream(targetFile));
if (!ini.equals(targetFile)) {
ini.delete();
descriptor.replace(ini, targetFile);
}
} catch (FileNotFoundException e) {
System.out.println("Impossible to brand ini file"); //$NON-NLS-1$
return;
} catch (IOException e) {
System.out.println("Impossible to brand ini file"); //$NON-NLS-1$
return;
}
}
private void modifyInfoPListFile(ExecutablesDescriptor descriptor, File initialRoot, File targetRoot, String iconName) {
File infoPList = new File(initialRoot, "Info.plist"); //$NON-NLS-1$
InfoPListEditor infoPListEditor = null;
try {
infoPListEditor = InfoPListEditor.loadPListEditor(infoPList);
} catch (IOException e) {
System.out.println("Impossible to create info.plist editor for " + infoPList.getAbsolutePath() + ". Caused by " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
String capitalizedName = name.substring(0, 1).toUpperCase() + name.substring(1);
infoPListEditor.setKey(InfoPListEditor.MARKER_KEY, name);
infoPListEditor.setKey(InfoPListEditor.BUNDLE_KEY, capitalizedName);
infoPListEditor.setKey(InfoPListEditor.BUNDLE_DISPLAYNAME_KEY, capitalizedName);
infoPListEditor.setKey(InfoPListEditor.BUNDLE_ID_KEY, id == null ? name : id);
if (description != null)
infoPListEditor.setKey(InfoPListEditor.BUNDLE_INFO_KEY, description);
if (version != null) {
// CFBundleShortVersionString is to be 3 segments only
// http://developer.apple.com/library/mac/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-111349
StringBuffer sv = new StringBuffer(version.getSegmentCount() > 0 ? version.getSegment(0).toString() : "0"); //$NON-NLS-1$
sv.append('.');
sv.append(version.getSegmentCount() > 1 ? version.getSegment(1).toString() : "0"); //$NON-NLS-1$
sv.append('.');
sv.append(version.getSegmentCount() > 2 ? version.getSegment(2).toString() : "0"); //$NON-NLS-1$
infoPListEditor.setKey(InfoPListEditor.BUNDLE_VERSION_KEY, version.toString());
infoPListEditor.setKey(InfoPListEditor.BUNDLE_SHORT_VERSION_KEY, sv.toString());
}
if (iconName.length() > 0) {
infoPListEditor.setKey(InfoPListEditor.ICON_KEY, iconName);
}
File target = new File(targetRoot, "Info.plist"); //$NON-NLS-1$;
try {
target.getParentFile().mkdirs();
infoPListEditor.save(target);
} catch (TransformerException e) {
System.out.println("Impossible to save info.plist file " + target.getAbsolutePath()); //$NON-NLS-1$
return;
}
try {
if (!infoPList.getCanonicalFile().equals(target.getCanonicalFile()))
infoPList.delete();
} catch (IOException e) {
//ignore
}
descriptor.replace(infoPList, target);
}
private int scan(StringBuffer buf, int start, String targetName) {
return scan(buf, start, new String[] {targetName});
}
private int scan(StringBuffer buf, int start, String[] targets) {
for (int i = start; i < buf.length(); i++) {
for (int j = 0; j < targets.length; j++) {
if (i < buf.length() - targets[j].length()) {
String match = buf.substring(i, i + targets[j].length());
if (targets[j].equalsIgnoreCase(match))
return i;
}
}
}
return -1;
}
private StringBuffer readFile(File targetName) throws IOException {
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(targetName)));
StringBuffer result = new StringBuffer();
char[] buf = new char[4096];
int count;
try {
count = reader.read(buf, 0, buf.length);
while (count != -1) {
result.append(buf, 0, count);
count = reader.read(buf, 0, buf.length);
}
} finally {
try {
reader.close();
} catch (IOException e) {
// ignore exceptions here
}
}
return result;
}
private void transferStreams(InputStream source, OutputStream destination) throws IOException {
source = new BufferedInputStream(source);
destination = new BufferedOutputStream(destination);
try {
byte[] buffer = new byte[8192];
while (true) {
int bytesRead = -1;
if ((bytesRead = source.read(buffer)) == -1)
break;
destination.write(buffer, 0, bytesRead);
}
} finally {
try {
source.close();
} catch (IOException e) {
// ignore
}
try {
destination.close();
} catch (IOException e) {
// ignore
}
}
}
public void setOS(String value) {
os = value;
}
public void setId(String id) {
this.id = id;
}
public void setVersion(Version version) {
this.version = version;
}
public void setDescription(String description) {
this.description = description;
}
}