1 package org.codehaus.mojo.osxappbundle;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import org.apache.maven.artifact.Artifact;
21 import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
22 import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
23 import org.apache.maven.plugin.AbstractMojo;
24 import org.apache.maven.plugin.MojoExecutionException;
25 import org.apache.maven.project.MavenProject;
26 import org.apache.maven.project.MavenProjectHelper;
27 import org.apache.velocity.VelocityContext;
28 import org.apache.velocity.exception.MethodInvocationException;
29 import org.apache.velocity.exception.ParseErrorException;
30 import org.apache.velocity.exception.ResourceNotFoundException;
31 import org.codehaus.plexus.archiver.ArchiverException;
32 import org.codehaus.plexus.archiver.zip.ZipArchiver;
33 import org.codehaus.plexus.util.DirectoryScanner;
34 import org.codehaus.plexus.util.FileUtils;
35 import org.codehaus.plexus.util.cli.CommandLineException;
36 import org.codehaus.plexus.util.cli.Commandline;
37 import org.codehaus.plexus.velocity.VelocityComponent;
38 import org.codehaus.mojo.osxappbundle.encoding.DefaultEncodingDetector;
39
40 import java.io.File;
41 import java.io.FileWriter;
42 import java.io.IOException;
43 import java.io.StringWriter;
44 import java.io.ByteArrayInputStream;
45 import java.io.Writer;
46 import java.io.OutputStreamWriter;
47 import java.io.FileOutputStream;
48 import java.util.ArrayList;
49 import java.util.Iterator;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.Arrays;
53
54
55
56
57
58
59
60
61 public class CreateApplicationBundleMojo
62 extends AbstractMojo
63 {
64
65
66
67
68 private static final String[] DEFAULT_INCLUDES = {"**/**"};
69
70
71
72
73
74
75
76 private MavenProject project;
77
78
79
80
81
82
83 private File buildDirectory;
84
85
86
87
88
89
90 private File diskImageFile;
91
92
93
94
95
96
97
98 private File javaApplicationStub;
99
100
101
102
103
104
105
106 private String mainClass;
107
108
109
110
111
112
113
114
115 private String bundleName;
116
117
118
119
120
121
122
123 private File iconFile;
124
125
126
127
128
129
130 private String version;
131
132
133
134
135
136
137 private String jvmVersion;
138
139
140
141
142
143
144 private File zipFile;
145
146
147
148
149
150
151
152
153
154 private List additionalClasspath;
155
156
157
158
159
160
161
162
163 private List additionalResources;
164
165
166
167
168
169
170
171 private VelocityComponent velocity;
172
173
174
175
176
177
178
179 private String dictionaryFile;
180
181
182
183
184
185
186 private String vmOptions;
187
188
189
190
191
192
193
194
195 private MavenProjectHelper projectHelper;
196
197
198
199
200
201
202
203
204 private ZipArchiver zipArchiver;
205
206
207
208
209
210
211
212 private boolean internetEnable;
213
214
215
216
217 private static final String SET_FILE_PATH = "/Developer/Tools/SetFile";
218
219
220
221
222
223
224
225 public void execute()
226 throws MojoExecutionException
227 {
228
229
230 buildDirectory.mkdirs();
231
232 File bundleDir = new File( buildDirectory, bundleName + ".app" );
233 bundleDir.mkdirs();
234
235 File contentsDir = new File( bundleDir, "Contents" );
236 contentsDir.mkdirs();
237
238 File resourcesDir = new File( contentsDir, "Resources" );
239 resourcesDir.mkdirs();
240
241 File javaDirectory = new File( resourcesDir, "Java" );
242 javaDirectory.mkdirs();
243
244 File macOSDirectory = new File( contentsDir, "MacOS" );
245 macOSDirectory.mkdirs();
246
247
248 File stub = new File( macOSDirectory, javaApplicationStub.getName() );
249 if(! javaApplicationStub.exists()) {
250 String message = "Can't find JavaApplicationStub binary. File does not exist: " + javaApplicationStub;
251
252 if(! isOsX() ) {
253 message += "\nNOTICE: You are running the osxappbundle plugin on a non OS X platform. To make this work you need to copy the JavaApplicationStub binary into your source tree. Then configure it with the 'javaApplicationStub' configuration property.\nOn an OS X machine, the JavaApplicationStub is typically located under /System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub";
254 }
255
256 throw new MojoExecutionException( message);
257
258 } else {
259 try
260 {
261 FileUtils.copyFile( javaApplicationStub, stub );
262 }
263 catch ( IOException e )
264 {
265 throw new MojoExecutionException(
266 "Could not copy file " + javaApplicationStub + " to directory " + macOSDirectory, e );
267 }
268 }
269
270
271 if ( iconFile != null )
272 {
273 try
274 {
275 FileUtils.copyFileToDirectory( iconFile, resourcesDir );
276 }
277 catch ( IOException e )
278 {
279 throw new MojoExecutionException( "Error copying file " + iconFile + " to " + resourcesDir, e );
280 }
281 }
282
283
284 List files = copyDependencies( javaDirectory );
285
286
287 File infoPlist = new File( bundleDir, "Contents/Info.plist" );
288 writeInfoPlist( infoPlist, files );
289
290
291 if (additionalResources != null && !additionalResources.isEmpty())
292 {
293 copyResources( additionalResources );
294 }
295
296 if ( isOsX() )
297 {
298
299 Commandline chmod = new Commandline();
300 try
301 {
302 chmod.setExecutable( "chmod" );
303 chmod.createArgument().setValue( "755" );
304 chmod.createArgument().setValue( stub.getAbsolutePath() );
305
306 chmod.execute();
307 }
308 catch ( CommandLineException e )
309 {
310 throw new MojoExecutionException( "Error executing " + chmod + " ", e );
311 }
312
313
314 if ( new File( SET_FILE_PATH ).exists() )
315 {
316 Commandline setFile = new Commandline();
317 try
318 {
319 setFile.setExecutable(SET_FILE_PATH);
320 setFile.createArgument().setValue( "-a B" );
321 setFile.createArgument().setValue( bundleDir.getAbsolutePath() );
322
323 setFile.execute();
324 }
325 catch ( CommandLineException e )
326 {
327 throw new MojoExecutionException( "Error executing " + setFile, e );
328 }
329 }
330 else
331 {
332 getLog().warn( "Could not set 'Has Bundle' attribute. " +SET_FILE_PATH +" not found, is Developer Tools installed?" );
333 }
334
335 Commandline dmg = new Commandline();
336 try
337 {
338 dmg.setExecutable( "hdiutil" );
339 dmg.createArgument().setValue( "create" );
340 dmg.createArgument().setValue( "-srcfolder" );
341 dmg.createArgument().setValue( buildDirectory.getAbsolutePath() );
342 dmg.createArgument().setValue( diskImageFile.getAbsolutePath() );
343 try
344 {
345 dmg.execute().waitFor();
346 }
347 catch ( InterruptedException e )
348 {
349 throw new MojoExecutionException( "Thread was interrupted while creating DMG " + diskImageFile, e );
350 }
351 }
352 catch ( CommandLineException e )
353 {
354 throw new MojoExecutionException( "Error creating disk image " + diskImageFile, e );
355 }
356 if(internetEnable) {
357 try {
358
359 Commandline internetEnable = new Commandline();
360
361 internetEnable.setExecutable("hdiutil");
362 internetEnable.createArgument().setValue("internet-enable" );
363 internetEnable.createArgument().setValue("-yes");
364 internetEnable.createArgument().setValue(diskImageFile.getAbsolutePath());
365
366 internetEnable.execute();
367 } catch (CommandLineException e) {
368 throw new MojoExecutionException("Error internet enabling disk image: " + diskImageFile, e);
369 }
370 }
371 projectHelper.attachArtifact(project, "dmg", null, diskImageFile);
372 }
373
374 zipArchiver.setDestFile( zipFile );
375 try
376 {
377 String[] stubPattern = {buildDirectory.getName() + "/" + bundleDir.getName() +"/Contents/MacOS/"
378 + javaApplicationStub.getName()};
379
380 zipArchiver.addDirectory( buildDirectory.getParentFile(), new String[]{buildDirectory.getName() + "/**"},
381 stubPattern);
382
383 DirectoryScanner scanner = new DirectoryScanner();
384 scanner.setBasedir( buildDirectory.getParentFile() );
385 scanner.setIncludes( stubPattern);
386 scanner.scan();
387
388 String[] stubs = scanner.getIncludedFiles();
389 for ( int i = 0; i < stubs.length; i++ )
390 {
391 String s = stubs[i];
392 zipArchiver.addFile( new File( buildDirectory.getParentFile(), s ), s, 0755 );
393 }
394
395 zipArchiver.createArchive();
396 projectHelper.attachArtifact(project, "zip", null, zipFile);
397 }
398 catch ( ArchiverException e )
399 {
400 throw new MojoExecutionException( "Could not create zip archive of application bundle in " + zipFile, e );
401 }
402 catch ( IOException e )
403 {
404 throw new MojoExecutionException( "IOException creating zip archive of application bundle in " + zipFile,
405 e );
406 }
407
408
409 }
410
411 private boolean isOsX()
412 {
413 return System.getProperty( "mrj.version" ) != null;
414 }
415
416
417
418
419
420
421
422
423 private List copyDependencies( File javaDirectory )
424 throws MojoExecutionException
425 {
426
427 ArtifactRepositoryLayout layout = new DefaultRepositoryLayout();
428
429 List list = new ArrayList();
430
431 File repoDirectory = new File(javaDirectory, "repo");
432 repoDirectory.mkdirs();
433
434
435 File artifactFile = project.getArtifact().getFile();
436 list.add( repoDirectory.getName() +"/" +layout.pathOf(project.getArtifact()));
437
438 try
439 {
440 FileUtils.copyFile( artifactFile, new File(repoDirectory, layout.pathOf(project.getArtifact())) );
441 }
442 catch ( IOException e )
443 {
444 throw new MojoExecutionException( "Could not copy artifact file " + artifactFile + " to " + javaDirectory );
445 }
446
447 Set artifacts = project.getArtifacts();
448
449 Iterator i = artifacts.iterator();
450
451 while ( i.hasNext() )
452 {
453 Artifact artifact = (Artifact) i.next();
454
455 File file = artifact.getFile();
456 File dest = new File(repoDirectory, layout.pathOf(artifact));
457
458 getLog().debug( "Adding " + file );
459
460 try
461 {
462 FileUtils.copyFile( file, dest);
463 }
464 catch ( IOException e )
465 {
466 throw new MojoExecutionException( "Error copying file " + file + " into " + javaDirectory, e );
467 }
468
469 list.add( repoDirectory.getName() +"/" + layout.pathOf(artifact) );
470 }
471
472 return list;
473
474 }
475
476
477
478
479
480
481
482
483 private void writeInfoPlist( File infoPlist, List files )
484 throws MojoExecutionException
485 {
486
487 VelocityContext velocityContext = new VelocityContext();
488
489 velocityContext.put( "mainClass", mainClass );
490 velocityContext.put( "cfBundleExecutable", javaApplicationStub.getName());
491 velocityContext.put( "vmOptions", vmOptions);
492 velocityContext.put( "bundleName", bundleName );
493
494 velocityContext.put( "iconFile", iconFile == null ? "GenericJavaApp.icns" : iconFile.getName() );
495
496 velocityContext.put( "version", version );
497
498 velocityContext.put( "jvmVersion", jvmVersion );
499
500 StringBuffer jarFilesBuffer = new StringBuffer();
501
502 jarFilesBuffer.append( "<array>" );
503 for ( int i = 0; i < files.size(); i++ )
504 {
505 String name = (String) files.get( i );
506 jarFilesBuffer.append( "<string>" );
507 jarFilesBuffer.append( "$JAVAROOT/" ).append( name );
508 jarFilesBuffer.append( "</string>" );
509
510 }
511 if ( additionalClasspath != null )
512 {
513 for ( int i = 0; i < additionalClasspath.size(); i++ )
514 {
515 String pathElement = (String) additionalClasspath.get( i );
516 jarFilesBuffer.append( "<string>" );
517 jarFilesBuffer.append( pathElement );
518 jarFilesBuffer.append( "</string>" );
519
520 }
521 }
522 jarFilesBuffer.append( "</array>" );
523
524 velocityContext.put( "classpath", jarFilesBuffer.toString() );
525
526 try
527 {
528
529 String encoding = detectEncoding(dictionaryFile, velocityContext);
530
531 getLog().debug( "Detected encoding " + encoding + " for dictionary file " +dictionaryFile );
532
533 Writer writer = new OutputStreamWriter( new FileOutputStream(infoPlist), encoding );
534
535 velocity.getEngine().mergeTemplate( dictionaryFile, encoding, velocityContext, writer );
536
537 writer.close();
538 }
539 catch ( IOException e )
540 {
541 throw new MojoExecutionException( "Could not write Info.plist to file " + infoPlist, e );
542 }
543 catch ( ParseErrorException e )
544 {
545 throw new MojoExecutionException( "Error parsing " + dictionaryFile, e );
546 }
547 catch ( ResourceNotFoundException e )
548 {
549 throw new MojoExecutionException( "Could not find resource for template " + dictionaryFile, e );
550 }
551 catch ( MethodInvocationException e )
552 {
553 throw new MojoExecutionException(
554 "MethodInvocationException occured merging Info.plist template " + dictionaryFile, e );
555 }
556 catch ( Exception e )
557 {
558 throw new MojoExecutionException( "Exception occured merging Info.plist template " + dictionaryFile, e );
559 }
560
561 }
562
563 private String detectEncoding( String dictionaryFile, VelocityContext velocityContext )
564 throws Exception
565 {
566 StringWriter sw = new StringWriter();
567 velocity.getEngine().mergeTemplate( dictionaryFile, "utf-8", velocityContext, sw );
568 return new DefaultEncodingDetector().detectXmlEncoding( new ByteArrayInputStream(sw.toString().getBytes( "utf-8" )) );
569 }
570
571
572
573
574
575
576
577 private void copyResources( List fileSets )
578 throws MojoExecutionException
579 {
580 final String[] emptyStrArray = {};
581
582 for ( Iterator it = fileSets.iterator(); it.hasNext(); )
583 {
584 FileSet fileSet = (FileSet) it.next();
585
586 File resourceDirectory = new File( fileSet.getDirectory() );
587 if ( !resourceDirectory.isAbsolute() )
588 {
589 resourceDirectory = new File( project.getBasedir(), resourceDirectory.getPath() );
590 }
591
592 if ( !resourceDirectory.exists() )
593 {
594 getLog().info( "Additional resource directory does not exist: " + resourceDirectory );
595 continue;
596 }
597
598 DirectoryScanner scanner = new DirectoryScanner();
599
600 scanner.setBasedir( resourceDirectory );
601 if ( fileSet.getIncludes() != null && !fileSet.getIncludes().isEmpty() )
602 {
603 scanner.setIncludes( (String[]) fileSet.getIncludes().toArray( emptyStrArray ) );
604 }
605 else
606 {
607 scanner.setIncludes( DEFAULT_INCLUDES );
608 }
609
610 if ( fileSet.getExcludes() != null && !fileSet.getExcludes().isEmpty() )
611 {
612 scanner.setExcludes( (String[]) fileSet.getExcludes().toArray( emptyStrArray ) );
613 }
614
615 if (fileSet.isUseDefaultExcludes())
616 {
617 scanner.addDefaultExcludes();
618 }
619
620 scanner.scan();
621
622 List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
623
624 getLog().info( "Copying " + includedFiles.size() + " additional resource"
625 + ( includedFiles.size() > 1 ? "s" : "" ) );
626
627 for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
628 {
629 String destination = (String) j.next();
630 File source = new File( resourceDirectory, destination );
631 File destinationFile = new File( buildDirectory, destination );
632
633 if ( !destinationFile.getParentFile().exists() )
634 {
635 destinationFile.getParentFile().mkdirs();
636 }
637
638 try
639 {
640 FileUtils.copyFile(source, destinationFile);
641 }
642 catch ( IOException e )
643 {
644 throw new MojoExecutionException( "Error copying additional resource " + source, e );
645 }
646 }
647 }
648 }
649
650 }