package org.codehaus.mojo.gwt.shell; /* * CompileMojo.java * * Created on January 13, 2007, 11:42 AM * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * To change this template, choose Tools | Template Manager * and open the template in the editor. * */ import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.codehaus.mojo.gwt.GwtModule; import org.codehaus.mojo.gwt.utils.GwtModuleReaderException; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping; import org.codehaus.plexus.util.StringUtils; import java.io.File; import java.util.Collection; import java.util.HashSet; /** * Invokes the GWT Compiler for the project source. * See compiler options : * http://www.gwtproject.org/doc/latest/DevGuideCompilingAndDebugging.html#DevGuideCompilerOptions * * @version $Id$ * @author cooper * @author ccollins * @author <a href="mailto:nicolas@apache.org">Nicolas De loof</a> * @author <a href="mailto:olamy@apache.org">Olivier Lamy</a> */ @Mojo(name = "compile", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true) public class CompileMojo extends AbstractGwtShellMojo { @Parameter(property = "gwt.compiler.skip", defaultValue = "false") private boolean skip; /** * Don't try to detect if GWT compilation is up-to-date and can be skipped. * <p> * Can be set from command line using '-Dgwt.compiler.force=true'. */ @Parameter(property = "gwt.compiler.force", defaultValue = "false") private boolean force; /** * On GWT 1.6+, number of parallel processes used to compile GWT premutations. Defaults to * platform available processors number. * * <p> * Can be unset from command line using '-Dgwt.compiler.localWorkers=n'. * </p> */ @Parameter(property = "gwt.compiler.localWorkers") private int localWorkers; /** * Whether or not to enable assertions in generated scripts (-checkAssertions). */ @Parameter(alias = "enableAssertions", defaultValue = "false") private boolean checkAssertions; /** * EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName()). * <p> * Can be set from command line using '-Dgwt.disableClassMetadata=true'. * </p> */ @Parameter(defaultValue = "false", property = "gwt.disableClassMetadata") private boolean disableClassMetadata; /** * EXPERIMENTAL: Disables run-time checking of cast operations. * <p> * Can be set from command line using '-Dgwt.disableCastChecking=true'. * </p> */ @Parameter(defaultValue = "false", property = "gwt.disableCastChecking") private boolean disableCastChecking; /** * EXPERIMENTAL: Disables code-splitting. * <p> * Can be set from command line using '-Dgwt.disableRunAsync=true'. * </p> */ @Parameter(defaultValue = "false", property = "gwt.disableRunAsync") private boolean disableRunAsync; /** * Validate all source code, but do not compile. * <p> * Can be set from command line using '-Dgwt.validateOnly=true'. * </p> */ @Parameter(defaultValue = "false", property = "gwt.validateOnly") private boolean validateOnly; /** * Enable faster, but less-optimized, compilations. * <p> * Can be set from command line using '-Dgwt.draftCompile=true'. * </p> */ @Parameter(defaultValue = "false", property = "gwt.draftCompile") private boolean draftCompile; /** * The directory into which extra, non-deployed files will be written. */ @Parameter(defaultValue = "${project.build.directory}/extra") private File extra; /** * The compiler's working directory for internal use (must be writeable; defaults to a system temp dir) */ @Parameter private File workDir; /** * add -extra parameter to the compiler command line * <p> * Can be set from command line using '-Dgwt.extraParam=true'. * * @since 2.1.0-1 */ @Parameter(defaultValue = "false", property = "gwt.extraParam") private boolean extraParam; /** * Compile a report that tells the "Story of Your Compile". * <p> * Can be set from command line using '-Dgwt.compiler.compileReport=true'. * </p> * * @since 2.1.0-1 */ @Parameter(defaultValue = "false", property = "gwt.compiler.compileReport") private boolean compileReport; /** * Sets the optimization level used by the compiler. 0=none 9=maximum. * <p> * -1 uses the default level of the compiler. * </p> * <p> * Can be set from command line using '-Dgwt.compiler.optimizationLevel=n'. * </p> * @parameter default-value="-1" expression="${gwt.compiler.optimizationLevel}" * @since 2.1.0-1 */ @Parameter(defaultValue = "-1", property = "gwt.compiler.optimizationLevel") private int optimizationLevel; /** * EXPERIMENTAL: Emit extra, detailed compile-report information in the "Story Of Your Compile" at the expense of compile time. * <p> * Can be set from command line using '-Dgwt.compiler.soycDetailed=true'. * </p> * * @since 2.1.0-1 */ @Parameter(alias = "soycDetailed", defaultValue = "false", property = "gwt.compiler.soycDetailed") private boolean detailedSoyc; /** * Fail compilation if any input file contains an error. * * <p> * Can be set from command line using '-Dgwt.compiler.strict=true'. * </p> * * @since 2.1.0-1 */ @Parameter(alias = "strict", defaultValue = "false", property = "gwt.compiler.strict") private boolean failOnError; /** * EXPERIMENTAL: Compile output Javascript with the Closure compiler for even further optimizations. * <p> * Can be set from the command line using '-Dgwt.compiler.enableClosureCompiler=true' * </p> * * @since 2.5.0-rc1 * @deprecated https://github.com/gwtproject/gwt/commit/162ccc9c9112a09bf9ea046da95760f5f1886b72 */ @Parameter(alias = "enableClosureCompiler", defaultValue = "false", property = "gwt.compiler.enableClosureCompiler") private boolean closureCompiler; /** * Enables Javascript output suitable for post-compilation by Closure Compiler. * * @since 2.8.0 */ @Parameter(defaultValue = "false", property = "gwt.compiler.closureFormattedOutput") private boolean closureFormattedOutput; /** * EXPERIMENTAL: Gather compiler metrics. * <p> * Can be set from the command line using '-Dgwt.compiler.compilerMetrics=true' * </p> * * @since 2.5.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.compiler.compilerMetrics") private boolean compilerMetrics; /** * EXPERIMENTAL: Limits of number of fragments using a code splitter that merges split points. * <p> * Can be set from the command line using '-Dgwt.compiler.fragmentCount=n' * </p> * * @since 2.5.0-rc1 */ @Parameter(defaultValue = "-1", property = "gwt.compiler.fragmentCount") private int fragmentCount; /** * EXPERIMENTAL: Cluster similar functions in the output to improve compression. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.clusterFunctions") private boolean clusterFunctions; /** * EXPERIMENTAL: Inline literal parameters to shrink function declarations and * provide more deadcode elimination possibilities. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.inlineLiteralParameters") private boolean inlineLiteralParameters; /** * EXPERIMENTAL: Analyze and optimize dataflow. * * since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.optimizeDataflow") private boolean optimizeDataflow; /** * Generate exports for JsInterop purposes. * * @since 2.8.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.compiler.generateJsInteropExports") private boolean generateJsInteropExports; /** * EXPERIMENTAL: Ordinalize enums to reduce some large strings. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.ordinalizeEnums") private boolean ordinalizeEnums; /** * EXPERIMENTAL: Removing duplicate functions. * <p> * Will interfere with stacktrace deobfuscation and so is only honored when compiler.stackMode is set to strip. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.removeDuplicateFunctions") private boolean removeDuplicateFunctions; /** * Enables saving source code needed by debuggers. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.saveSource") private boolean saveSource; /** * Overrides where source files useful to debuggers will be written. * <p> * Default: saved with extras. * * @since 2.6.0-rc2 */ @Parameter private File saveSourceOutput; /** * Specifies Java source level. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "auto", property = "maven.compiler.source") private String sourceLevel; /** * Puts most JavaScript globals into namespaces. * <p> * Value is one of PACKAGE or NONE. * <p> * Default: PACKAGE for -draftCompile, otherwise NONE * * @since 2.7.0-rc1 */ @Parameter private String namespace; @Parameter(defaultValue = "false") private boolean overlappingSourceWarnings; /** * EXPERIMENTAL: Emit detailed compile-report information in the "Story Of Your Compile" in the new json format. * * @since 2.7.0-rc1 */ @Parameter(defaultValue = "false") private boolean enableJsonSoyc; /** * Compiles faster by reusing data from the previous compile. * * @since 2.7.0-rc1 */ @Parameter(alias = "compilePerFile", defaultValue = "false", property = "gwt.compiler.incremental") private boolean incremental; /** * EXPERIMENTAL: Emit extra information allow chrome dev tools to display Java identifiers in many places instead of JavaScript functions. * <p> * Value can be one of NONE, ONLY_METHOD_NAME, ABBREVIATED or FULL. * * @since 2.7.0-rc1 */ @Parameter(defaultValue = "NONE", property = "gwt.compiler.methodNameDisplayMode") private String methodNameDisplayMode; /** * Indicates whether to print the whole Java command used for GWT compilation, in case of an error. If set to false, * the command is not printed. * * Ability to disable the command comes handy in case the build uses big number (hundreds) of jars. In that case the * classpath string is huge and the printed command is polluting the Maven build log. */ @Parameter(defaultValue = "true", property = "gwt.compiler.printJavaCommandOnError" ) private boolean printJavaCommandOnError; public void doExecute( ) throws MojoExecutionException, MojoFailureException { if ( skip || "pom".equals( getProject().getPackaging() ) ) { getLog().info( "GWT compilation is skipped" ); return; } if ( !this.getOutputDirectory().exists() ) { this.getOutputDirectory().mkdirs(); } compile( getModules() ); } @Override protected String getExtraJvmArgs() { String jvmArgs = super.getExtraJvmArgs(); // workaround to GWT issue 4031 with IBM JDK // @see https://code.google.com/p/google-web-toolkit/issues/detail?id=4031#c16 if ( System.getProperty( "java.vendor" ).startsWith( "IBM" ) && StringUtils.isEmpty(getJvm()) && !StringUtils.isEmpty( jvmArgs )) { return jvmArgs + " -Dgwt.jjs.javaArgs=" + StringUtils.quoteAndEscape( jvmArgs, '"', new char[] { '"', ' ', '\t', '\r', '\n' } ); } return jvmArgs; } private void compile( String[] modules ) throws MojoExecutionException { boolean upToDate = true; JavaCommand cmd = createJavaCommand() .setMainClass( "com.google.gwt.dev.Compiler" ); if ( gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } cmd.addToClasspath( getClasspath( Artifact.SCOPE_COMPILE ) ); if ( !gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } cmd.arg( "-logLevel", getLogLevel() ) .arg( "-war", getOutputDirectory().getAbsolutePath() ) .arg( "-localWorkers", String.valueOf( getLocalWorkers() ) ) // optional advanced arguments .arg( checkAssertions, "-checkAssertions" ) .arg( draftCompile, "-draftCompile" ) .arg( validateOnly, "-validateOnly" ) .arg( disableClassMetadata, "-XnoclassMetadata" ) .arg( disableCastChecking, "-XnocheckCasts" ) .arg( disableRunAsync, "-XnocodeSplitting" ) .arg( failOnError, "-failOnError" ) .arg( detailedSoyc, "-XdetailedSoyc" ) .arg( closureCompiler, "-XclosureCompiler" ) .arg( closureFormattedOutput, "-XclosureFormattedOutput" ) .arg( compileReport, "-compileReport" ) .arg( compilerMetrics, "-XcompilerMetrics" ) .arg( "-XfragmentCount", String.valueOf( fragmentCount ) ) .arg( !clusterFunctions, "-XnoclusterFunctions" ) .arg( !inlineLiteralParameters, "-XnoinlineLiteralParameters" ) .arg( !optimizeDataflow, "-XnooptimizeDataflow" ) .arg( !ordinalizeEnums, "-XnoordinalizeEnums" ) .arg( !removeDuplicateFunctions, "-XnoremoveDuplicateFunctions" ) .arg( saveSource, "-saveSource" ) .arg( "-sourceLevel", sourceLevel ) .arg( enableJsonSoyc, "-XenableJsonSoyc" ) .arg( incremental, "-incremental" ) .arg( generateJsInteropExports, "-generateJsInteropExports" ) ; if ( style != null && style.length() > 0 ) { cmd.arg( "-style", style ); } if ( methodNameDisplayMode != null && methodNameDisplayMode.length() > 0 && !methodNameDisplayMode.equals( "NONE" )) { cmd.arg( "-XmethodNameDisplayMode", methodNameDisplayMode ); } if ( namespace != null && namespace.length() > 0 ) { cmd.arg( "-Xnamespace", namespace ); } if ( saveSourceOutput != null ) { cmd.arg( "-saveSourceOutput", saveSourceOutput.getAbsolutePath() ); } if ( optimizationLevel >= 0 ) { cmd.arg( "-optimize" ).arg( Integer.toString( optimizationLevel ) ); } if ( extraParam || compileReport || ( saveSource && saveSourceOutput == null ) ) { getLog().debug( "create extra directory " ); if ( !extra.exists() ) { extra.mkdirs(); } cmd.arg( "-extra" ).arg( extra.getAbsolutePath() ); } else { getLog().debug( "NOT create extra directory " ); } addCompileSourceArtifacts( cmd ); addArgumentDeploy(cmd); addArgumentGen( cmd ); addPersistentUnitCache(cmd); if ( workDir != null ) { cmd.arg( "-workDir" ).arg( String.valueOf( workDir ) ); } for ( String target : modules ) { if ( !compilationRequired( target, getOutputDirectory() ) ) { continue; } cmd.arg( target ); upToDate = false; } cmd.setPrintCommandOnError(printJavaCommandOnError); if ( !upToDate ) { try { cmd.execute(); } catch ( JavaCommandException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } } private int getLocalWorkers() { if ( localWorkers > 0 ) { return localWorkers; } return Runtime.getRuntime().availableProcessors(); } /** * Try to find out, if there are stale sources. If aren't some, we don't have to compile... ...this heuristic * doesn't take into account, that there could be updated dependencies. But for this case, as 'clean compile' could * be executed which would force a compilation. * * @param module Name of the GWT module to compile * @param output Output path * @return true if compilation is required (i.e. stale sources are found) * @throws MojoExecutionException When sources scanning fails * @author Alexander Gordt */ private boolean compilationRequired( String module, File output ) throws MojoExecutionException { getLog().debug( "**Checking if compilation is required for " + module ); try { GwtModule gwtModule = readModule( module ); if ( gwtModule.getEntryPoints().size() == 0 ) { getLog().info( gwtModule.getName() + " has no EntryPoint - compilation skipped" ); // No entry-point, this is an utility module : compiling this one will fail // with '[ERROR] Module has no entry points defined' return false; } getLog().debug( "Module has an entrypoint" ); if ( force ) { return true; } getLog().debug( "Compilation not forced"); String modulePath = gwtModule.getPath(); String outputTarget = modulePath + "/" + modulePath + ".nocache.js"; File outputTargetFile = new File( output, outputTarget ); // Require compilation if no js file present in target. if ( !outputTargetFile.exists() ) { return true; } getLog().debug( "Output file exists"); File moduleFile = gwtModule.getSourceFile(); if(moduleFile == null) { return true; //the module was read from something like an InputStream; always recompile this because we can't make any other choice } getLog().debug( "There is a module source file (not an input stream"); //If input is newer than target, recompile if(moduleFile.lastModified() > outputTargetFile.lastModified()) { getLog().debug( "Module file has been modified since the output file was created; recompiling" ); return true; } getLog().debug( "The module XML hasn't been updated"); // js file already exists, but may not be up-to-date with project source files SingleTargetSourceMapping singleTargetMapping = new SingleTargetSourceMapping( ".java", outputTarget ); StaleSourceScanner scanner = new StaleSourceScanner(); scanner.addSourceMapping( singleTargetMapping ); SingleTargetSourceMapping uiBinderMapping = new SingleTargetSourceMapping( ".ui.xml", outputTarget ); scanner.addSourceMapping( uiBinderMapping ); Collection<File> compileSourceRoots = new HashSet<File>(); for (String sourceRoot : getProject().getCompileSourceRoots()) { for (String sourcePackage : gwtModule.getSources()) { String packagePath = gwtModule.getPackage().replace( '.', File.separatorChar ); File sourceDirectory = new File (sourceRoot + File.separatorChar + packagePath + File.separator + sourcePackage); if(sourceDirectory.exists()) { getLog().debug(" Looking in a source directory "+sourceDirectory.getAbsolutePath() + " for possible changes"); compileSourceRoots.add(sourceDirectory); } } } for ( File sourceRoot : compileSourceRoots ) { if ( !sourceRoot.isDirectory() ) { continue; } try { if ( !scanner.getIncludedSources( sourceRoot, output ).isEmpty() ) { getLog().debug( "found stale source in " + sourceRoot + " compared with " + output ); return true; } } catch ( InclusionScanException e ) { throw new MojoExecutionException( "Error scanning source root: \'" + sourceRoot + "\' " + "for stale files to recompile.", e ); } } getLog().info( module + " is up to date. GWT compilation skipped" ); return false; } catch ( GwtModuleReaderException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } }