View Javadoc

1   // Copyright (C) 2004, Brian Enigma <enigma at netninja.com>
2   // This file is part of Mac-Package.
3   //
4   // Mac-Package is free software; you can redistribute it and/or modify
5   // it under the terms of the GNU General Public License as published by
6   // the Free Software Foundation; either version 2 of the License, or
7   // (at your option) any later version.
8   //
9   // Mac-Package is distributed in the hope that it will be useful,
10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  // GNU General Public License for more details.
13  //
14  // You should have received a copy of the GNU General Public License
15  // along with Foobar; if not, write to the Free Software
16  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  package org.ninjasoft.macpackager;
18  
19  import java.io.*;
20  import java.util.*;
21  
22  /***
23   * Ant task that will create a Mac OS X application bundle.
24   * This task expects a nested fileset of *.jar files
25   * 
26   * Requires: jar files (in nested fileset)
27   *           Version - Application version
28   *           InfoString - Name, version, copyright information
29   *           Icon file - *.icns file containing application icon(s)
30   *           VM Options - Optional things to pass to the VM (name in app menu, shiny metal look, etc)
31   *           mainclass - main class
32   *  
33   * Derives:  BundleIdentifier: from mainclass + version
34   *           
35   * @author Brian Enigma
36   */
37  public MacPackager {/package-summary.html">class MacPackager {
38      private String appName = null;
39      private String version = null;
40      private String infoString = null;
41      private String icon = null;
42      private String vmOptions = null;
43      private String mainClass = null;
44      
45      privateInfoBuilder builder = new InfoBuilder()/package-summary.html">ong> InfoBuilder builder = new InfoBuilder();
46      private String appLocation;
47      private Vector jarFiles = new Vector();
48      
49      //private static final String appExt = ".app";
50      private static final String appExt = "";
51      
52      public void buildApplication() throws PackageException {
53          // Sanity-check the input
54          validate();
55          // Stuff the builder full of data
56          builder.setBundleName(this.appName);
57          builder.setVersion(this.version);
58          builder.setInfoString(this.infoString);
59          builder.setVmOptions(this.vmOptions);
60          builder.setMainClass(this.mainClass);
61          for (Iterator i = this.jarFiles.iterator(); i.hasNext(); )
62          	builder.addJarFile(i.next().toString());
63          String target = "target/" + appName + appExt;
64          if (new File(target).exists()) {
65              System.out.println("Removing existing application " + target);
66          	deleteRecursive(target);
67          }
68          if (new File(target).exists())
69              thPackageException(/package-summary.html">row new PackageException("Unable to delete " + target);
70          // Make the structure
71          mkdirs();
72          // Copy in the relevant files
73          copyFiles();
74          // Create the relevant new files
75          createFiles();
76          // Tag the folder as an application
77          tagFolder();
78          System.out.println("Application bundle ready");
79      }
80      
81      private void deleteRecursive(String s) {
82          if ((s == null) || (s.length() == 0) || s.equals(".") || s.equals(".."))
83              return;
84          File f = new File(s);
85          if (f.isDirectory()) {
86              String files[] = f.list();
87              for (int i=0; i<files.length; i++)
88                  deleteRecursive(s + "/" + files[i]);
89              f.delete();
90          } else {
91              f.delete();
92          }
93      }
94      
95      /***
96       * Create the target directories
97       * @throws BuildException
98       */
99      private void mkdirs() throws PackageException {
100         appLocation = "target/" + appName + appExt + "/Contents/MacOS";
101         File d = new File(appLocation);
102         if (!d.mkdirs())
103             thPackageException(/package-summary.html">row new PackageException("Unable to create folder " + d.getPath());
104         d = new File("target/" + appName + appExt + "/Contents/Resources/Java");
105         if (!d.mkdirs())
106             thPackageException(/package-summary.html">row new PackageException("Unable to create folder " + d.getPath());
107     }
108     
109     private void copyFiles() throws PackageException {
110         // Copy the OS X Java application stub
111         File s = new File("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub");
112         File d = new File("target/" + appName + appExt + "/Contents/MacOS/JavaApplicationStub");
113     	copy(s, d);
114         String[] command = new String[] {
115                 "/bin/chmod",
116                 "0755",
117                 d.getPath()
118         };
119         // Set the execute bits on the Java application stub
120         try{
121             Process p = Runtime.getRuntime().exec(command);
122             int rc = p.waitFor();
123             if (rc != 0)
124                 PackageException(/package-summary.html">g>throw new PackageException("chmod of JavaApplicationStub returned error code " + rc);
125         }catch(IOException ioe){
126             thPackageException(/package-summary.html">row new PackageException("chmod of JavaApplicationStub threw an IOException");
127         }catch(InterruptedException ie){
128             thPackageException(/package-summary.html">row new PackageException("chmod of JavaApplicationStub threw an InterruptedException");
129         }
130         // Copy the icon file
131         s = new File(icon);
132         d = new File("target/" + appName + appExt + "/Contents/Resources/icon.icns");
133         copy(s, d);
134         // Copy the jar files over
135         for (Iterator i = this.jarFiles.iterator(); i.hasNext(); ) {
136             s = new File(i.next().toString());
137             d = new File("target/" + appName + appExt + "/Contents/Resources/Java/" + s.getName());
138             copy(s, d);
139         }
140     }
141     
142     private void createFiles() throws PackageException {
143         try{
144             File f = new File("target/" + appName + appExt + "/Contents/PkgInfo");
145             FileOutputStream out = new FileOutputStream(f);
146             out.write("APPL????".getBytes());
147             out.close();
148         }catch(IOException e){
149             thPackageException(/package-summary.html">row new PackageException("Problem creating PkgInfo file: " + e.toString());
150         }
151         try{
152             File f = new File("target/" + appName + appExt + "/Contents/Info.plist");
153             FileOutputStream out = new FileOutputStream(f);
154             out.write(builder.getXml().getBytes());
155             out.close();
156         }catch(IOException e){
157             thPackageException(/package-summary.html">row new PackageException("Problem creating Info.plist XML file: " + e.toString());
158         }
159         
160     }
161     
162     /***
163      * Copy a source file to a destination file
164      * @param source
165      * @param destination
166      * @throws BuildException
167      */
168     private void copy(File source, File destination) throws PackageException {
169         try{
170             FileInputStream in = new FileInputStream(source);
171             FileOutputStream out = new FileOutputStream(destination);
172             byte[] buff = new byte[1024];
173             int len;
174             while ((len = in.read(buff)) > 0)
175                 out.write(buff, 0, len);
176             in.close();
177             out.close();
178         }catch(IOException e){
179             thPackageException(/package-summary.html">row new PackageException("Unable to copy " + source.getPath() + " to " + destination.getPath() + " :: " + e.toString());
180         }
181     }
182     
183     /***
184      * Tag a folder as a bundle
185      * @throws BuildException
186      */
187     private void tagFolder() throws PackageException {
188         String[] command = new String[] {
189                 "/Developer/Tools/SetFile",
190                 "-a",
191                 "B",
192                 "target/" + appName + appExt
193         };
194         // Set the execute bits on the Java application stub
195         try{
196             Process p = Runtime.getRuntime().exec(command);
197             int rc = p.waitFor();
198             if (rc != 0)
199                 PackageException(/package-summary.html">g>throw new PackageException("chmod of JavaApplicationStub returned error code " + rc);
200         }catch(IOException ioe){
201             thPackageException(/package-summary.html">row new PackageException("chmod of JavaApplicationStub threw an IOException");
202         }catch(InterruptedException ie){
203             thPackageException(/package-summary.html">row new PackageException("chmod of JavaApplicationStub threw an InterruptedException");
204         }
205     }
206     
207     /***
208      * Check that all of the required attributes have been set, source files exist, and
209      * required executables exist.
210      * 
211      * @throws BuildException
212      */
213     public void validate() throws PackageException {
214         if (appName == null)            throw new PackageException("appName attribute required");
215         if (version == null)            throw new PackageException("version attribute required");
216         if (infoString == null)         throw new PackageException("infoString attribute required");
217         if (icon == null)               throw new PackageException("icon attribute required");
218         if (vmOptions == null)          vmOptions = "";
219         if (mainClass == null)          throw new PackageException("mainClass attribute required");
220         if (jarFiles.size() == 0)       throw new PackageException("There should be at least one jar file.  Use a nested FileSet.");
221         File check = new File(icon);
222         if (!check.exists())
223             thPackageException(/package-summary.html">row new PackageException("Unable to locate icon file " + icon);
224         check = new File("/bin/chmod");
225         if (!check.exists())
226             thPackageException(/package-summary.html">row new PackageException("Unable to locate /bin/chmod");
227         check = new File("/Developer/Tools/SetFile");
228         if (!check.exists())
229             thPackageException(/package-summary.html">row new PackageException("/Developer/Tools/SetFile command does not exist.  Please install the OS X Developer Tools.");
230     }
231     
232     // #####################################################################
233     // Setters
234     public void addJar(String jar) {
235         this.jarFiles.add(jar);
236     }
237     
238 	public void setIcon(String icon) {
239 		this.icon = icon;
240 	}
241 	public void setInfoString(String infoString) {
242 		this.infoString = infoString;
243 	}
244 	public void setMainClass(String mainClass) {
245 		this.mainClass = mainClass;
246 	}
247 	public void setVersion(String version) {
248 		this.version = version;
249 	}
250 	public void setVmOptions(String vmOptions) {
251 		this.vmOptions = vmOptions;
252 	}
253 	public void setAppName(String appName) {
254 		this.appName = appName;
255 	}
256 }