blob: 415f0a0b2d432023cea107afcacc85962e78da2e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
*******************************************************************************/
package org.eclipse.jdt.internal.ui.macbundler;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.IProgressMonitor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class BundleBuilder implements BundleAttributes {
private List<Process> fProcesses= new ArrayList<>();
private BundleDescription fBundleDescription;
/**
* Create a new bundle
* @param bd the new description
* @param pm progress monitor
* @throws IOException if something happens
*/
public void createBundle(BundleDescription bd, IProgressMonitor pm) throws IOException {
fBundleDescription= bd;
File tmp_dir= new File(bd.get(DESTINATIONDIRECTORY));
String app_dir_name= bd.get(APPNAME) + ".app"; //$NON-NLS-1$
File app_dir= new File(tmp_dir, app_dir_name);
if (app_dir.exists()) {
deleteDir(app_dir);
}
app_dir= createDir(tmp_dir, app_dir_name, false);
File contents_dir= createDir(app_dir, "Contents", false); //$NON-NLS-1$
createPkgInfo(contents_dir);
File macos_dir= createDir(contents_dir, "MacOS", false); //$NON-NLS-1$
String launcher_path= bd.get(LAUNCHER);
if (launcher_path == null) {
throw new IOException();
}
String launcher= copyFile(macos_dir, launcher_path, null);
File resources_dir= createDir(contents_dir, "Resources", false); //$NON-NLS-1$
File java_dir= createDir(resources_dir, "Java", false); //$NON-NLS-1$
createInfoPList(contents_dir, resources_dir, java_dir, launcher);
Iterator<Process> iter= fProcesses.iterator();
while (iter.hasNext()) {
Process p= iter.next();
try {
p.waitFor();
} catch (InterruptedException e) {
// silently ignore
}
}
}
private void createInfoPList(File contents_dir, File resources_dir, File java_dir, String launcher) throws IOException {
DocumentBuilder docBuilder= null;
DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance();
factory.setValidating(false);
try {
docBuilder= factory.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
System.err.println("createInfoPList: could not get XML builder"); //$NON-NLS-1$
throw new IOException("Could not get XML builder"); //$NON-NLS-1$
}
Document doc= docBuilder.newDocument();
Element plist= doc.createElement("plist"); //$NON-NLS-1$
doc.appendChild(plist);
plist.setAttribute("version", "1.0"); //$NON-NLS-1$ //$NON-NLS-2$
Element dict= doc.createElement("dict"); //$NON-NLS-1$
plist.appendChild(dict);
pair(dict, "CFBundleExecutable", null, launcher); //$NON-NLS-1$
pair(dict, "CFBundleGetInfoString", GETINFO, null); //$NON-NLS-1$
pair(dict, "CFBundleInfoDictionaryVersion", null, "6.0"); //$NON-NLS-1$ //$NON-NLS-2$
String iconName= null;
String appName= fBundleDescription.get(APPNAME, null);
if (appName != null)
{
iconName= appName + ".icns"; //$NON-NLS-1$
}
String fname= copyFile(resources_dir, fBundleDescription.get(ICONFILE, null), iconName);
if (fname != null)
{
pair(dict, "CFBundleIconFile", null, fname); //$NON-NLS-1$
}
pair(dict, "CFBundleIdentifier", IDENTIFIER, null); //$NON-NLS-1$
pair(dict, "CFBundleName", APPNAME, null); //$NON-NLS-1$
pair(dict, "CFBundlePackageType", null, "APPL"); //$NON-NLS-1$ //$NON-NLS-2$
pair(dict, "CFBundleShortVersionString", VERSION, null); //$NON-NLS-1$
pair(dict, "CFBundleSignature", SIGNATURE, "????"); //$NON-NLS-1$ //$NON-NLS-2$
pair(dict, "CFBundleVersion", null, "1.0.1"); //$NON-NLS-1$ //$NON-NLS-2$
Element jdict= doc.createElement("dict"); //$NON-NLS-1$
add(dict, "Java", jdict); //$NON-NLS-1$
pair(jdict, "JVMVersion", JVMVERSION, null); //$NON-NLS-1$
pair(jdict, "MainClass", MAINCLASS, null); //$NON-NLS-1$
pair(jdict, "WorkingDirectory", WORKINGDIR, null); //$NON-NLS-1$
if (fBundleDescription.get(USES_SWT, false))
{
addTrue(jdict, "StartOnMainThread"); //$NON-NLS-1$
}
String arguments= fBundleDescription.get(ARGUMENTS, null);
if (arguments != null) {
Element argArray= doc.createElement("array"); //$NON-NLS-1$
add(jdict, "Arguments", argArray); //$NON-NLS-1$
StringTokenizer st= new StringTokenizer(arguments);
while (st.hasMoreTokens()) {
String arg= st.nextToken();
Element type= doc.createElement("string"); //$NON-NLS-1$
argArray.appendChild(type);
type.appendChild(doc.createTextNode(arg));
}
}
pair(jdict, "VMOptions", VMOPTIONS, null); //$NON-NLS-1$
int[] id= new int[] { 0 };
ResourceInfo[] ris= fBundleDescription.getResources(true);
if (ris.length > 0) {
StringBuilder cp= new StringBuilder();
for (int i= 0; i < ris.length; i++) {
ResourceInfo ri= ris[i];
String e= processClasspathEntry(java_dir, ri.fPath, id);
if (cp.length() > 0) {
cp.append(':');
}
cp.append(e);
}
add(jdict, "ClassPath", cp.toString()); //$NON-NLS-1$
}
ris= fBundleDescription.getResources(false);
if (ris.length > 0) {
for (int i= 0; i < ris.length; i++) {
ResourceInfo ri= ris[i];
processClasspathEntry(java_dir, ri.fPath, id);
}
}
File info= new File(contents_dir, "Info.plist"); //$NON-NLS-1$
try (FileOutputStream fos = new FileOutputStream(info); BufferedOutputStream fOutputStream = new BufferedOutputStream(fos);) {
// Write the document to the stream
Transformer transformer= TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Apple Computer//DTD PLIST 1.0//EN"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.apple.com/DTDs/PropertyList-1.0.dtd"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$
DOMSource source= new DOMSource(doc);
StreamResult result= new StreamResult(fOutputStream);
transformer.transform(source, result);
} catch (TransformerException e) {
System.err.println("createInfoPList: could not transform to XML"); //$NON-NLS-1$
}
}
private void add(Element dict, String key, Element value) {
Document document= dict.getOwnerDocument();
Element k= document.createElement("key"); //$NON-NLS-1$
dict.appendChild(k);
k.appendChild(document.createTextNode(key));
dict.appendChild(value);
}
private void create(Element parent, String s) {
Document document= parent.getOwnerDocument();
Element type= document.createElement("string"); //$NON-NLS-1$
parent.appendChild(type);
type.appendChild(document.createTextNode(s));
}
private void createTrue(Element parent) {
Document document= parent.getOwnerDocument();
Element type= document.createElement("true"); //$NON-NLS-1$
parent.appendChild(type);
}
private void add(Element dict, String key, String value) {
Document document= dict.getOwnerDocument();
Element k= document.createElement("key"); //$NON-NLS-1$
dict.appendChild(k);
k.appendChild(document.createTextNode(key));
create(dict, value);
}
private void addTrue(Element dict, String key) {
Document document= dict.getOwnerDocument();
Element k= document.createElement("key"); //$NON-NLS-1$
dict.appendChild(k);
k.appendChild(document.createTextNode(key));
createTrue(dict);
}
private void pair(Element dict, String outkey, String inkey, String dflt) {
String value= null;
if (inkey != null) {
value= fBundleDescription.get(inkey, dflt);
} else {
value= dflt;
}
if (value != null && value.trim().length() > 0) {
add(dict, outkey, value);
}
}
private String processClasspathEntry(File java_dir, String name, int[] id_ref) throws IOException {
File f= new File(name);
if (f.isDirectory()) {
int id= id_ref[0]++;
String archivename= "jar_" + id + ".jar"; //$NON-NLS-1$ //$NON-NLS-2$
File to= new File(java_dir, archivename);
zip(name, to.getAbsolutePath());
name= archivename;
} else {
name= copyFile(java_dir, name, null);
}
return "$JAVAROOT/" + name; //$NON-NLS-1$
}
private void createPkgInfo(File contents_dir) throws IOException {
File pkgInfo= new File(contents_dir, "PkgInfo"); //$NON-NLS-1$
try (FileOutputStream os = new FileOutputStream(pkgInfo)) {
os.write(("APPL" + fBundleDescription.get(SIGNATURE, "????")).getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private static void deleteDir(File dir) {
File[] files= dir.listFiles();
if (files != null) {
for (int i= 0; i < files.length; i++) {
deleteDir(files[i]);
}
}
dir.delete();
}
private File createDir(File parent_dir, String dir_name, boolean remove) throws IOException {
File dir= new File(parent_dir, dir_name);
if (dir.exists()) {
if (!remove) {
return dir;
}
deleteDir(dir);
}
if (! dir.mkdir())
{
throw new IOException("cannot create dir " + dir_name); //$NON-NLS-1$
}
return dir;
}
private String copyFile(File todir, String fromPath, String toname) throws IOException {
if (toname == null) {
int pos= fromPath.lastIndexOf('/');
if (pos >= 0) {
toname= fromPath.substring(pos+1);
} else {
toname= fromPath;
}
}
File to= new File(todir, toname);
fProcesses.add(Runtime.getRuntime().exec(new String[] { "/bin/cp", fromPath, to.getAbsolutePath() })); //$NON-NLS-1$
return toname;
}
private void zip(String dir, String dest) throws IOException {
fProcesses.add(Runtime.getRuntime().exec(new String[] { "/usr/bin/jar", "cf", dest, "-C", dir, "." })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}