| /******************************************************************************* |
| * 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$ |
| } |
| } |