View Javadoc
1   package org.codehaus.mojo.gwt.shell;
2   
3   /*
4    * CompileMojo.java
5    *
6    * Created on January 13, 2007, 11:42 AM
7    *
8    * This library is free software; you can redistribute it and/or
9    * modify it under the terms of the GNU Lesser General Public
10   * License as published by the Free Software Foundation; either
11   * version 2.1 of the License, or (at your option) any later version.
12   *
13   * This library is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this library; if not, write to the Free Software
20   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21   * To change this template, choose Tools | Template Manager
22   * and open the template in the editor.
23   *
24   */
25  
26  import org.apache.maven.artifact.Artifact;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.LifecyclePhase;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.plugins.annotations.ResolutionScope;
33  import org.codehaus.mojo.gwt.GwtModule;
34  import org.codehaus.mojo.gwt.utils.GwtModuleReaderException;
35  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
36  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
37  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  import java.io.File;
41  import java.util.Collection;
42  import java.util.HashSet;
43  
44  /**
45   * Invokes the GWT Compiler for the project source.
46   * See compiler options :
47   * http://www.gwtproject.org/doc/latest/DevGuideCompilingAndDebugging.html#DevGuideCompilerOptions
48   *
49   * @version $Id$
50   * @author cooper
51   * @author ccollins
52   * @author <a href="mailto:nicolas@apache.org">Nicolas De loof</a>
53   * @author <a href="mailto:olamy@apache.org">Olivier Lamy</a>
54   */
55  @Mojo(name = "compile", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
56  public class CompileMojo
57      extends AbstractGwtShellMojo
58  {
59  
60      @Parameter(property = "gwt.compiler.skip", defaultValue = "false")
61      private boolean skip;
62  
63      /**
64       * Don't try to detect if GWT compilation is up-to-date and can be skipped.
65       * <p>
66       * Can be set from command line using '-Dgwt.compiler.force=true'.
67       */
68      @Parameter(property = "gwt.compiler.force", defaultValue = "false")
69      private boolean force;
70  
71      /**
72       * On GWT 1.6+, number of parallel processes used to compile GWT premutations. Defaults to
73       * platform available processors number.
74       * 
75       * <p>
76       * Can be unset from command line using '-Dgwt.compiler.localWorkers=n'.
77       * </p>
78       */
79      @Parameter(property = "gwt.compiler.localWorkers")
80      private int localWorkers;
81  
82      /**
83       * Whether or not to enable assertions in generated scripts (-checkAssertions).
84       */
85      @Parameter(alias = "enableAssertions", defaultValue = "false")
86      private boolean checkAssertions;
87  
88      /**
89       * EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName()).
90       * <p>
91       * Can be set from command line using '-Dgwt.disableClassMetadata=true'.
92       * </p>
93       */
94      @Parameter(defaultValue = "false", property = "gwt.disableClassMetadata")
95      private boolean disableClassMetadata;
96  
97      /**
98       * EXPERIMENTAL: Disables run-time checking of cast operations.
99       * <p>
100      * Can be set from command line using '-Dgwt.disableCastChecking=true'.
101      * </p>
102      */
103     @Parameter(defaultValue = "false", property = "gwt.disableCastChecking")
104     private boolean disableCastChecking;
105 
106     /**
107      * EXPERIMENTAL: Disables code-splitting.
108      * <p>
109      * Can be set from command line using '-Dgwt.disableRunAsync=true'.
110      * </p>
111      */
112     @Parameter(defaultValue = "false", property = "gwt.disableRunAsync")
113     private boolean disableRunAsync;
114 
115     /**
116      * Validate all source code, but do not compile.
117      * <p>
118      * Can be set from command line using '-Dgwt.validateOnly=true'.
119      * </p>
120      */
121     @Parameter(defaultValue = "false", property = "gwt.validateOnly")
122     private boolean validateOnly;
123 
124     /**
125      * Enable faster, but less-optimized, compilations.
126      * <p>
127      * Can be set from command line using '-Dgwt.draftCompile=true'.
128      * </p>
129      */
130     @Parameter(defaultValue = "false", property = "gwt.draftCompile")
131     private boolean draftCompile;
132 
133     /**
134      * The directory into which extra, non-deployed files will be written.
135      */
136     @Parameter(defaultValue = "${project.build.directory}/extra")
137     private File extra;
138 
139     /**
140      * The compiler's working directory for internal use (must be writeable; defaults to a system temp dir)
141      */
142     @Parameter
143     private File workDir;
144 
145     /**
146      * add -extra parameter to the compiler command line
147      * <p>
148      * Can be set from command line using '-Dgwt.extraParam=true'.
149      *
150      * @since 2.1.0-1
151      */
152     @Parameter(defaultValue = "false", property = "gwt.extraParam")
153     private boolean extraParam;
154 
155     /**
156      * Compile a report that tells the "Story of Your Compile".
157      * <p>
158      * Can be set from command line using '-Dgwt.compiler.compileReport=true'.
159      * </p>
160      *
161      * @since 2.1.0-1
162      */
163     @Parameter(defaultValue = "false", property = "gwt.compiler.compileReport")
164     private boolean compileReport;
165 
166     /**
167      * Sets the optimization level used by the compiler.  0=none 9=maximum.
168      * <p>
169      * -1 uses the default level of the compiler.
170      * </p>
171      * <p>
172      * Can be set from command line using '-Dgwt.compiler.optimizationLevel=n'.
173      * </p>
174      * @parameter default-value="-1" expression="${gwt.compiler.optimizationLevel}"
175      * @since 2.1.0-1
176      */
177     @Parameter(defaultValue = "-1", property = "gwt.compiler.optimizationLevel")
178     private int optimizationLevel;
179 
180     /**
181      * EXPERIMENTAL: Emit extra, detailed compile-report information in the "Story Of Your Compile" at the expense of compile time.
182      * <p>
183      * Can be set from command line using '-Dgwt.compiler.soycDetailed=true'.
184      * </p>
185      *
186      * @since 2.1.0-1
187      */
188     @Parameter(alias = "soycDetailed", defaultValue = "false", property = "gwt.compiler.soycDetailed")
189     private boolean detailedSoyc;
190 
191     /**
192      * Fail compilation if any input file contains an error.
193      * 
194      * <p>
195      * Can be set from command line using '-Dgwt.compiler.strict=true'.
196      * </p>
197      *
198      * @since 2.1.0-1
199      */
200     @Parameter(alias = "strict", defaultValue = "false", property = "gwt.compiler.strict")
201     private boolean failOnError;
202 
203     /**
204      * EXPERIMENTAL: Compile output Javascript with the Closure compiler for even further optimizations.
205      * <p>
206      * Can be set from the command line using '-Dgwt.compiler.enableClosureCompiler=true'
207      * </p>
208      *
209      * @since 2.5.0-rc1
210      * @deprecated https://github.com/gwtproject/gwt/commit/162ccc9c9112a09bf9ea046da95760f5f1886b72
211      */
212     @Parameter(alias = "enableClosureCompiler", defaultValue = "false", property = "gwt.compiler.enableClosureCompiler")
213     private boolean closureCompiler;
214 
215     /**
216      * Enables Javascript output suitable for post-compilation by Closure Compiler.
217      *
218      * @since 2.8.0
219      */
220     @Parameter(defaultValue = "false", property = "gwt.compiler.closureFormattedOutput")
221     private boolean closureFormattedOutput;
222 
223     /**
224      * EXPERIMENTAL: Gather compiler metrics.
225      * <p>
226      * Can be set from the command line using '-Dgwt.compiler.compilerMetrics=true'
227      * </p>
228      *
229      * @since 2.5.0-rc1
230      */
231     @Parameter(defaultValue = "false", property = "gwt.compiler.compilerMetrics")
232     private boolean compilerMetrics;
233 
234     /**
235      * EXPERIMENTAL: Limits of number of fragments using a code splitter that merges split points.
236      * <p>
237      * Can be set from the command line using '-Dgwt.compiler.fragmentCount=n'
238      * </p>
239      *
240      * @since 2.5.0-rc1
241      */
242     @Parameter(defaultValue = "-1", property = "gwt.compiler.fragmentCount")
243     private int fragmentCount;
244 
245     /**
246      * EXPERIMENTAL: Cluster similar functions in the output to improve compression.
247      *
248      * @since 2.6.0-rc1
249      */
250     @Parameter(defaultValue = "true", property = "gwt.compiler.clusterFunctions")
251     private boolean clusterFunctions;
252 
253     /**
254      * EXPERIMENTAL: Inline literal parameters to shrink function declarations and
255      * provide more deadcode elimination possibilities.
256      *
257      * @since 2.6.0-rc1
258      */
259     @Parameter(defaultValue = "true", property = "gwt.compiler.inlineLiteralParameters")
260     private boolean inlineLiteralParameters;
261 
262     /**
263      * EXPERIMENTAL: Analyze and optimize dataflow.
264      *
265      * since 2.6.0-rc1
266      */
267     @Parameter(defaultValue = "true", property = "gwt.compiler.optimizeDataflow")
268     private boolean optimizeDataflow;
269 
270     /**
271      * Generate exports for JsInterop purposes.
272      *
273      * @since 2.8.0-rc1
274      */
275     @Parameter(defaultValue = "false", property = "gwt.compiler.generateJsInteropExports")
276     private boolean generateJsInteropExports;
277 
278     /**
279      * EXPERIMENTAL: Ordinalize enums to reduce some large strings.
280      *
281      * @since 2.6.0-rc1
282      */
283     @Parameter(defaultValue = "true", property = "gwt.compiler.ordinalizeEnums")
284     private boolean ordinalizeEnums;
285 
286     /**
287      * EXPERIMENTAL: Removing duplicate functions.
288      * <p>
289      * Will interfere with stacktrace deobfuscation and so is only honored when compiler.stackMode is set to strip.
290      *
291      * @since 2.6.0-rc1
292      */
293     @Parameter(defaultValue = "true", property = "gwt.compiler.removeDuplicateFunctions")
294     private boolean removeDuplicateFunctions;
295 
296     /**
297      * Enables saving source code needed by debuggers.
298      *
299      * @since 2.6.0-rc1
300      */
301     @Parameter(defaultValue = "false", property = "gwt.saveSource")
302     private boolean saveSource;
303 
304     /**
305      * Overrides where source files useful to debuggers will be written.
306      * <p>
307      * Default: saved with extras.
308      *
309      * @since 2.6.0-rc2
310      */
311     @Parameter
312     private File saveSourceOutput;
313 
314     /**
315      * Specifies Java source level.
316      *
317      * @since 2.6.0-rc1
318      */
319     @Parameter(defaultValue = "auto", property = "maven.compiler.source")
320     private String sourceLevel;
321 
322     /**
323      * Puts most JavaScript globals into namespaces.
324      * <p>
325      * Value is one of PACKAGE or NONE.
326      * <p>
327      * Default: PACKAGE for -draftCompile, otherwise NONE
328      * 
329      * @since 2.7.0-rc1
330      */
331     @Parameter
332     private String namespace;
333 
334     @Parameter(defaultValue = "false")
335     private boolean overlappingSourceWarnings;
336 
337     /**
338      * EXPERIMENTAL: Emit detailed compile-report information in the "Story Of Your Compile"  in the new json format.
339      * 
340      * @since 2.7.0-rc1
341      */
342     @Parameter(defaultValue = "false")
343     private boolean enableJsonSoyc;
344 
345     /**
346      * Compiles faster by reusing data from the previous compile.
347      * 
348      * @since 2.7.0-rc1
349      */
350     @Parameter(alias = "compilePerFile", defaultValue = "false", property = "gwt.compiler.incremental")
351     private boolean incremental;
352 
353     /**
354      * EXPERIMENTAL: Emit extra information allow chrome dev tools to display Java identifiers in many places instead of JavaScript functions.
355      * <p>
356      * Value can be one of NONE, ONLY_METHOD_NAME, ABBREVIATED or FULL.
357      * 
358      * @since 2.7.0-rc1
359      */
360     @Parameter(defaultValue = "NONE", property = "gwt.compiler.methodNameDisplayMode")
361     private String methodNameDisplayMode;
362 
363     /**
364      * Indicates whether to print the whole Java command used for GWT compilation, in case of an error. If set to false,
365      * the command is not printed.
366      *
367      * Ability to disable the command comes handy in case the build uses big number (hundreds) of jars. In that case the
368      * classpath string is huge and the printed command is polluting the Maven build log.
369      */
370     @Parameter(defaultValue = "true", property = "gwt.compiler.printJavaCommandOnError" )
371     private boolean printJavaCommandOnError;
372 
373     public void doExecute( )
374         throws MojoExecutionException, MojoFailureException
375     {
376         if ( skip || "pom".equals( getProject().getPackaging() ) )
377         {
378             getLog().info( "GWT compilation is skipped" );
379             return;
380         }
381 
382         if ( !this.getOutputDirectory().exists() )
383         {
384             this.getOutputDirectory().mkdirs();
385         }
386 
387         compile( getModules() );
388     }
389 
390     @Override
391     protected String getExtraJvmArgs()
392     {
393         String jvmArgs = super.getExtraJvmArgs();
394         // workaround to GWT issue 4031 with IBM JDK
395         // @see https://code.google.com/p/google-web-toolkit/issues/detail?id=4031#c16
396         if ( System.getProperty( "java.vendor" ).startsWith( "IBM" ) && StringUtils.isEmpty(getJvm()) && !StringUtils.isEmpty( jvmArgs ))
397         {
398             return jvmArgs + " -Dgwt.jjs.javaArgs=" + StringUtils.quoteAndEscape( jvmArgs, '"', new char[] { '"', ' ', '\t', '\r', '\n' } );
399         }
400         return jvmArgs;
401     }
402 
403     private void compile( String[] modules )
404         throws MojoExecutionException
405     {
406         boolean upToDate = true;
407 
408         JavaCommand cmd = createJavaCommand()
409             .setMainClass( "com.google.gwt.dev.Compiler" );
410         if ( gwtSdkFirstInClasspath )
411         {
412             cmd.addToClasspath( getGwtUserJar() )
413                .addToClasspath( getGwtDevJar() );
414         }
415         cmd.addToClasspath( getClasspath( Artifact.SCOPE_COMPILE ) );
416         if ( !gwtSdkFirstInClasspath )
417         {
418             cmd.addToClasspath( getGwtUserJar() )
419                .addToClasspath( getGwtDevJar() );
420         }
421 
422         cmd.arg( "-logLevel", getLogLevel() )
423             .arg( "-war", getOutputDirectory().getAbsolutePath() )
424             .arg( "-localWorkers", String.valueOf( getLocalWorkers() ) )
425             // optional advanced arguments
426             .arg( checkAssertions, "-checkAssertions" )
427             .arg( draftCompile, "-draftCompile" )
428             .arg( validateOnly, "-validateOnly" )
429             .arg( disableClassMetadata, "-XnoclassMetadata" )
430             .arg( disableCastChecking, "-XnocheckCasts" )
431             .arg( disableRunAsync, "-XnocodeSplitting" )
432             .arg( failOnError, "-failOnError" )
433             .arg( detailedSoyc, "-XdetailedSoyc" )
434             .arg( closureCompiler, "-XclosureCompiler" )
435             .arg( closureFormattedOutput, "-XclosureFormattedOutput" )
436             .arg( compileReport, "-compileReport" )
437             .arg( compilerMetrics, "-XcompilerMetrics" )
438             .arg( "-XfragmentCount", String.valueOf( fragmentCount ) )
439             .arg( !clusterFunctions, "-XnoclusterFunctions" )
440             .arg( !inlineLiteralParameters, "-XnoinlineLiteralParameters" )
441             .arg( !optimizeDataflow, "-XnooptimizeDataflow" )
442             .arg( !ordinalizeEnums, "-XnoordinalizeEnums" )
443             .arg( !removeDuplicateFunctions, "-XnoremoveDuplicateFunctions" )
444             .arg( saveSource, "-saveSource" )
445             .arg( "-sourceLevel", sourceLevel )
446             .arg( enableJsonSoyc, "-XenableJsonSoyc" )
447             .arg( incremental, "-incremental" )
448             .arg( generateJsInteropExports, "-generateJsInteropExports" )
449         ;
450 
451         if ( style != null && style.length() > 0 )
452         {
453             cmd.arg( "-style", style );
454         }
455 
456         if ( methodNameDisplayMode != null && methodNameDisplayMode.length() > 0 && !methodNameDisplayMode.equals( "NONE" ))
457         {
458             cmd.arg( "-XmethodNameDisplayMode", methodNameDisplayMode );
459         }
460 
461         if ( namespace != null && namespace.length() > 0 )
462         {
463             cmd.arg( "-Xnamespace", namespace );
464         }
465 
466         if ( saveSourceOutput != null )
467         {
468             cmd.arg( "-saveSourceOutput", saveSourceOutput.getAbsolutePath() );
469         }
470 
471         if ( optimizationLevel >= 0 )
472         {
473             cmd.arg( "-optimize" ).arg( Integer.toString( optimizationLevel ) );
474         }
475 
476         if ( extraParam || compileReport || ( saveSource && saveSourceOutput == null ) )
477         {
478             getLog().debug( "create extra directory " );
479             if ( !extra.exists() )
480             {
481                 extra.mkdirs();
482             }
483             cmd.arg( "-extra" ).arg( extra.getAbsolutePath() );
484         }
485         else
486         {
487             getLog().debug( "NOT create extra directory " );
488         }
489 
490         addCompileSourceArtifacts( cmd );
491         addArgumentDeploy(cmd);
492         addArgumentGen( cmd );
493         addPersistentUnitCache(cmd);
494 
495         if ( workDir != null )
496         {
497             cmd.arg( "-workDir" ).arg( String.valueOf( workDir ) );
498         }
499 
500         for ( String target : modules )
501         {
502             if ( !compilationRequired( target, getOutputDirectory() ) )
503             {
504                 continue;
505             }
506             cmd.arg( target );
507             upToDate = false;
508         }
509 
510         cmd.setPrintCommandOnError(printJavaCommandOnError);
511 
512         if ( !upToDate )
513         {
514             try
515             {
516                 cmd.execute();
517             }
518             catch ( JavaCommandException e )
519             {
520                 throw new MojoExecutionException( e.getMessage(), e );
521             }
522         }
523     }
524 
525     private int getLocalWorkers()
526     {
527         if ( localWorkers > 0 )
528         {
529             return localWorkers;
530         }
531         return Runtime.getRuntime().availableProcessors();
532     }
533 
534     /**
535      * Try to find out, if there are stale sources. If aren't some, we don't have to compile... ...this heuristic
536      * doesn't take into account, that there could be updated dependencies. But for this case, as 'clean compile' could
537      * be executed which would force a compilation.
538      *
539      * @param module Name of the GWT module to compile
540      * @param output Output path
541      * @return true if compilation is required (i.e. stale sources are found)
542      * @throws MojoExecutionException When sources scanning fails
543      * @author Alexander Gordt
544      */
545     private boolean compilationRequired( String module, File output )
546         throws MojoExecutionException
547     {
548         getLog().debug( "**Checking if compilation is required for " + module );
549         try
550         {
551 
552         	GwtModule gwtModule = readModule( module );
553             if ( gwtModule.getEntryPoints().size() == 0 )
554             {
555                 getLog().info( gwtModule.getName() + " has no EntryPoint - compilation skipped" );
556                 // No entry-point, this is an utility module : compiling this one will fail
557                 // with '[ERROR] Module has no entry points defined'
558                 return false;
559             }
560             getLog().debug( "Module has an entrypoint" );
561 
562             if ( force )
563             {
564                 return true;
565             }
566             getLog().debug( "Compilation not forced");
567             
568             String modulePath = gwtModule.getPath();
569 
570             String outputTarget = modulePath + "/" + modulePath + ".nocache.js";
571             File outputTargetFile = new File( output, outputTarget );
572             // Require compilation if no js file present in target.
573             if ( !outputTargetFile.exists() )
574             {
575                 return true;
576             }
577             getLog().debug( "Output file exists");
578             
579             File moduleFile = gwtModule.getSourceFile();
580             if(moduleFile == null) {
581             	return true; //the module was read from something like an InputStream; always recompile this because we can't make any other choice
582             }
583             getLog().debug( "There is a module source file (not an input stream");
584             
585             //If input is newer than target, recompile
586             if(moduleFile.lastModified() > outputTargetFile.lastModified()) 
587             {
588                 getLog().debug( "Module file has been modified since the output file was created; recompiling" );
589             	return true;
590             }
591             getLog().debug( "The module XML hasn't been updated");
592 
593             // js file already exists, but may not be up-to-date with project source files
594             SingleTargetSourceMapping singleTargetMapping = new SingleTargetSourceMapping( ".java", outputTarget );
595             StaleSourceScanner scanner = new StaleSourceScanner();
596             scanner.addSourceMapping( singleTargetMapping );
597 
598             SingleTargetSourceMapping uiBinderMapping = new SingleTargetSourceMapping( ".ui.xml", outputTarget );
599             scanner.addSourceMapping( uiBinderMapping );
600 
601             Collection<File> compileSourceRoots = new HashSet<File>();
602             for (String sourceRoot : getProject().getCompileSourceRoots()) {
603                 for (String sourcePackage : gwtModule.getSources()) {
604                     String packagePath = gwtModule.getPackage().replace( '.', File.separatorChar );
605                     File sourceDirectory = new File (sourceRoot + File.separatorChar + packagePath + File.separator + sourcePackage);
606                     if(sourceDirectory.exists()) {
607                         getLog().debug(" Looking in a source directory "+sourceDirectory.getAbsolutePath() + " for possible changes");
608                         compileSourceRoots.add(sourceDirectory);					
609                     }
610                 }
611             }
612 
613             for ( File sourceRoot : compileSourceRoots )
614             {
615                 if ( !sourceRoot.isDirectory() )
616                 {
617                     continue;
618                 }
619                 try
620                 {
621                     if ( !scanner.getIncludedSources( sourceRoot, output ).isEmpty() )
622                     {
623                         getLog().debug( "found stale source in " + sourceRoot + " compared with " + output );
624                         return true;
625                     }
626                 }
627                 catch ( InclusionScanException e )
628                 {
629                     throw new MojoExecutionException( "Error scanning source root: \'" + sourceRoot + "\' "
630                         + "for stale files to recompile.", e );
631                 }
632             }
633             getLog().info( module + " is up to date. GWT compilation skipped" );
634             return false;
635         }
636         catch ( GwtModuleReaderException e )
637         {
638             throw new MojoExecutionException( e.getMessage(), e );
639         }
640     }
641 }