All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jlgrock.javascriptframework.closurecompiler.JsClosureCompileMojo Maven / Gradle / Ivy

package com.github.jlgrock.javascriptframework.closurecompiler;

import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

import com.github.jlgrock.javascriptframework.mavenutils.logging.Log4jOutputStream;
import com.github.jlgrock.javascriptframework.mavenutils.logging.MojoLogAppender;
import com.github.jlgrock.javascriptframework.mavenutils.mavenobjects.JsarRelativeLocations;
import com.github.jlgrock.javascriptframework.mavenutils.pathing.FileListBuilder;
import com.google.common.base.Charsets;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
import com.google.javascript.jscomp.CommandLineRunner;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.JSSourceFile;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.WarningLevel;

/**
 * 
 * @goal js-closure-compile
 * @phase compile
 */
public class JsClosureCompileMojo extends AbstractMojo {
	/**
	 * The Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(JsClosureCompileMojo.class);

	/**
	 * A simple util to convert a collection of files to a list of closure
	 * JSSourceFiles.
	 * 
	 * @param jsFiles
	 *            the collection of files to convert
	 * @return the list of google formatted objects
	 */
	private static List convertToJSSourceFiles(
			final Collection jsFiles) {
		List jsSourceFiles = new ArrayList();
		for (File f : jsFiles) {
			jsSourceFiles.add(JSSourceFile.fromFile(f));
		}
		return jsSourceFiles;
	}

	/**
	 * Create the dependencies JS file.
	 * 
	 * @param baseLocation
	 *            the location of base.js
	 * @param src
	 *            the location of the source files
	 * @param interns
	 *            the internal dependencies
	 * @param depsFile
	 *            the location of the deps file
	 * @param requiresFile
	 *            the location of the requires file
	 * @return the list of dependencies, in dependency order
	 * @throws MojoExecutionException
	 *             if the dependency generator is not able to make a file
	 * @throws IOException
	 *             if there is a problem reading or writing to any of the files
	 */
	private static List createDepsAndRequiresJS(final File baseLocation,
			final Collection src, final Collection interns,
			final File depsFile, final File requiresFile)
			throws MojoExecutionException, IOException {

		// TODO when they fix the visibility rules in the DepsGenerator, replace
		// it with Google's version
		LOGGER.debug("base location: " + baseLocation);
		LOGGER.debug("src files: " + src);
		LOGGER.debug("intern files: " + interns);
		LOGGER.debug("deps file location: " + depsFile);

		return CalcDeps.executeCalcDeps(baseLocation, src, interns, depsFile,
				requiresFile);
	}

	/**
	 * Get the location of base.js.
	 * 
	 * @param closureLibraryLocation
	 *            the location of the google library
	 * @return the base.js file reference
	 * @throws MojoExecutionException
	 *             If it couldn't find base.js
	 */
	private static File getBaseLocation(final File closureLibraryLocation)
			throws MojoExecutionException {
		File baseLocation = new File(closureLibraryLocation.getAbsoluteFile()
				+ File.separator + "closure" + File.separator + "goog"
				+ File.separator + "base.js");
		if (!baseLocation.exists()) {
			throw new MojoExecutionException(
					"Could not locate \"base.js\" at location \""
							+ baseLocation.getParentFile().getAbsolutePath()
							+ "\"");
		}
		return baseLocation;
	}

	/**
	 * List the errors that google is providing from the compiler output.
	 * 
	 * @param result
	 *            the results from the compiler
	 */
	private static void listErrors(final Result result) {
		for (JSError warning : result.warnings) {
			LOGGER.warn("[Goog.WARN]: " + warning.toString());
		}

		for (JSError error : result.errors) {
			LOGGER.error("[Goog.ERROR]: " + error.toString());
		}
	}

	/**
	 * List the javascript files in a directory.
	 * 
	 * @param directory
	 *            the directory to search
	 * @return the set of files with the ".js" extension
	 */
	private static Set listFiles(final File directory) {
		return FileListBuilder.buildFilteredList(directory, "js");
	}

	/**
	 * The location of the closure library. By default, this is expected in the
	 * ${project.build.directory}${file.separator}javascriptFramework${file.
	 * separator}closure-library${file.separator}closure${file.separator}goog
	 * directory, as this is where the jsdependency plugin will put it by
	 * default. If you would like to override this and use your own location,
	 * this can be done by changing this path.
	 * 
	 * @parameter default-value=
	 *            "${project.build.directory}${file.separator}javascriptFramework${file.separator}closure-library"
	 * @required
	 */
	private File closureLibraryLocation;

	/**
	 * The file produced after running the dependencies and files through the
	 * compiler.
	 * 
	 * @parameter default-value="${project.build.finalName}-min.js"
	 * @required
	 */
	private String compiledFilename;

	/**
	 * Specifies the compiler level to use.
	 * 
	 * Possible values are:
	 * 
    *
  • WHITESPACE_ONLY *
  • SIMPLE_OPTIMIZATIONS *
  • ADVANCED_OPTIMIZATIONS *
*
* * Please see the Google Compiler levels page for more details. * * @parameter default-value="ADVANCED_OPTIMIZATIONS" * @required */ private String compileLevel; /** * Specifies how strict the compiler is at following the rules. The compiler * is set to parameters matching SIMPLE by default, however, if you don't * use WARNING or better, this isn't very useful. STRICT is set by default * for this mojo. * * Possible values are: *
    *
  • SIMPLE *
  • WARNING *
  • STRICT *
*
* * @parameter default-value="STRICT" */ private String errorLevel; /** * The file produced after running the dependencies and files through the * compiler. * * @parameter default-value= * "${project.build.directory}${file.separator}javascriptFramework" * @required */ private File frameworkTargetDirectory; /** * The file produced after running the dependencies and files through the * compiler. * * @parameter default-value="${project.build.finalName}-assert.js" * @required */ private String generatedAssertJS; /** * The file produced that allows inclusion of assert into non-closure based * systems. * * @parameter default-value="${project.build.finalName}-assert-requires.js" * @required */ private String generatedAssertRequiresJS; /** * The file produced after running the dependencies and files through the * compiler. * * @parameter default-value="${project.build.finalName}-debug.js" * @required */ private String generatedDebugJS; /** * The file produced that allows inclusion of debug into non-closure based * systems. * * @parameter default-value="${project.build.finalName}-debug-requires.js" * @required */ private String generatedDebugRequiresJS; /** * @parameter default-value="true" */ private boolean generateExports; /** * What to include based off of maven includes. If you are creating an API * with an empty src folder, or you expect to use all of the maven * dependencies as source, set this to ALL. * * Possible values are: *
    *
  • ALL *
  • WHEN_IN_SRCS *
*
* * @parameter default-value="WHEN_IN_SRCS" */ private String inclusionStrategy; /** * The default directory to extract files to. This likely shouldn't be * changed unless there is a conflict with another plugin. * * @parameter default-value= * "${basedir}${file.separator}src${file.separator}test${file.separator}javascript" * @required */ private File testSourceDirectory; /** * Will wrap the code in whatever you put in here. It uses '%output%' to * define what your code is. An example of this would be * "(function() {%output% window['my']['namespace'] = my.namespace;})();", * which would wrap the entire code in an anonymous function. * * @parameter default-value="" * @required */ private String outputWrapper = ""; /** * The string to match the code fragment in the outputWrapper parameter. */ private static final String OUTPUT_WRAPPER_MARKER = "%output%"; /** * Extract external dependency libraries to the location specified in the * settings. * * @return the list of the files that are extracted * @throws IOException * if unable to read the default externs */ private List calculateExternFiles() throws IOException { Set externSourceFiles = listFiles(JsarRelativeLocations .getExternsLocation(frameworkTargetDirectory)); List externalJSSourceFiles = convertToJSSourceFiles(externSourceFiles); externalJSSourceFiles.addAll(CommandLineRunner.getDefaultExterns()); LOGGER.debug("number of external files:" + externalJSSourceFiles.size()); return externalJSSourceFiles; } /** * Extract internal dependency libraries, source to the location specified * in the settings. Then create the deps to be loaded first. * * @param source * the collection of source files * @return the list of the files that are extracted (plus the generated deps * file) * @throws MojoExecutionException * if there is a problem generating the dependency file * @throws IOException * if there is a problem reading or extracting the files */ private Collection calculateInternalFiles( final Collection source) throws MojoExecutionException, IOException { Set internalSourceFiles = listFiles(JsarRelativeLocations .getInternsLocation(frameworkTargetDirectory)); LOGGER.debug("number of internal dependency files:" + internalSourceFiles.size()); Set closureLibFiles = listFiles(closureLibraryLocation); LOGGER.debug("number of google lib files:" + closureLibFiles.size()); HashSet combinedInternal = new HashSet(); combinedInternal.addAll(source); combinedInternal.addAll(internalSourceFiles); combinedInternal.addAll(closureLibFiles); return combinedInternal; } /** * Calculates the Source file collection. * * @param sourceDir * the source directory to scan * @param internsLocation * the internal dependency * @return the set of files calculated */ private Set calculateSourceFiles(final File sourceDir, final File internsLocation) { InclusionStrategy strategy = InclusionStrategy .getByType(inclusionStrategy); if (strategy == null) { strategy = InclusionStrategy.WHEN_IN_SRCS; } LOGGER.info("Calculating source files using Inclusion strategy: " + strategy); Set listSourceFiles = new HashSet(); if (strategy.equals(InclusionStrategy.WHEN_IN_SRCS)) { listSourceFiles.addAll(listFiles(sourceDir)); } else { listSourceFiles.addAll(listFiles(sourceDir)); listSourceFiles.addAll(listFiles(internsLocation)); } LOGGER.debug("number of source files:" + listSourceFiles.size()); return listSourceFiles; } /** * Run the compiler on the calculated dependencies, input files, and * external files. * * @param allSources * the source files to compile * @param externs * the external dependency javascript files * @return true if the compile works, false otherwise * @throws MojoExecutionException * if the options are set incorrectly for the compiler * @throws MojoFailureException * if there is a problem executing the dependency creation or * the compiler * @throws IOException * if there is a problem reading or writing to the files */ private boolean compile(final Collection allSources, final Collection externs) throws MojoExecutionException, MojoFailureException, IOException { CompilationLevel compilationLevel = null; try { compilationLevel = CompilationLevel.valueOf(compileLevel); LOGGER.info("Compiler set to optimization level \"" + compileLevel + "\"."); } catch (IllegalArgumentException e) { LOGGER.error("Compilation level invalid. Aborting."); throw new MojoExecutionException( "Compilation level invalid. Aborting."); } CompilerOptions compilerOptions = new CompilerOptions(); if (ErrorLevel.getCompileLevelByName(errorLevel).equals( ErrorLevel.WARNING)) { WarningLevel wLevel = WarningLevel.VERBOSE; wLevel.setOptionsForWarningLevel(compilerOptions); } else if (ErrorLevel.getCompileLevelByName(errorLevel).equals( ErrorLevel.STRICT)) { StrictLevel sLevel = StrictLevel.VERBOSE; sLevel.setOptionsForWarningLevel(compilerOptions); } compilationLevel.setOptionsForCompilationLevel(compilerOptions); compilerOptions.setGenerateExports(generateExports); PrintStream ps = new PrintStream(new Log4jOutputStream(LOGGER, Level.DEBUG)); Compiler compiler = new Compiler(ps); for (JSSourceFile jsf : allSources) { LOGGER.debug("source files: " + jsf.getOriginalPath()); } Result result = null; try { LOGGER.debug("externJSSourceFiles: " + externs); LOGGER.debug("allSources: " + allSources); result = compiler.compile( externs.toArray(new JSSourceFile[externs.size()]), allSources.toArray(new JSSourceFile[allSources.size()]), compilerOptions); } catch (Exception e) { LOGGER.error("There was a problem with the compile. Please review input."); e.printStackTrace(); throw new MojoExecutionException(e.getMessage(), e); } listErrors(result); if (!result.success) { return false; } File compiledFile = new File( JsarRelativeLocations .getCompileLocation(frameworkTargetDirectory), compiledFilename); Files.createParentDirs(compiledFile); Files.touch(compiledFile); JsClosureCompileMojo.writeOutput(compiledFile, compiler, outputWrapper, OUTPUT_WRAPPER_MARKER); return true; } @Override public final void execute() throws MojoExecutionException, MojoFailureException { MojoLogAppender.beginLogging(this); try { LOGGER.info("Compiling source files and internal dependencies to location \"" + JsarRelativeLocations.getCompileLocation( frameworkTargetDirectory).getAbsolutePath() + "\"."); // gather externs for both asserts and debug Collection externs = calculateExternFiles(); // get base location for closure library File baseLocation = getBaseLocation(closureLibraryLocation); // create assert file Collection assertSourceFiles = calculateSourceFiles( JsarRelativeLocations .getAssertionSourceLocation(frameworkTargetDirectory), JsarRelativeLocations .getInternsLocation(frameworkTargetDirectory)); File assertFile = getGeneratedAssertJS(); File assertRequiresFile = getGeneratedAssertRequiresJS(); Collection assertInternFiles = calculateInternalFiles(assertSourceFiles); createDepsAndRequiresJS(baseLocation, assertSourceFiles, assertInternFiles, assertFile, assertRequiresFile); // create debug file File debugFile = getGeneratedDebugJS(); File debugRequiresFile = getGeneratedDebugRequiresJS(); Collection sourceFiles = calculateSourceFiles( JsarRelativeLocations .getDebugSourceLocation(frameworkTargetDirectory), JsarRelativeLocations .getInternsLocation(frameworkTargetDirectory)); Collection debugInternFiles = calculateInternalFiles(sourceFiles); List debugDepsFiles = createDepsAndRequiresJS(baseLocation, sourceFiles, debugInternFiles, debugFile, debugRequiresFile); // create testing file File testDepsFile = getGeneratedTestJS(); Collection srcAndTest = new HashSet(); srcAndTest.addAll(assertSourceFiles); srcAndTest.addAll(FileListBuilder.buildFilteredList( testSourceDirectory, "js")); createDepsAndRequiresJS(baseLocation, srcAndTest, assertInternFiles, testDepsFile, null); // create file collection for compilation List debugFiles = new ArrayList(); debugFiles.add(getBaseLocation(closureLibraryLocation)); debugFiles.add(debugFile); debugFiles.addAll(debugDepsFiles); // compile debug into compiled dir boolean result = compile(convertToJSSourceFiles(debugFiles), externs); if (!result) { String message = "Google Closure Compilation failure. Please review errors to continue."; LOGGER.error(message); throw new MojoFailureException(message); } } catch (Exception e) { e.printStackTrace(); e.printStackTrace(new PrintStream(new Log4jOutputStream(LOGGER, Level.DEBUG))); throw new MojoExecutionException( "Unable to closure compile files: " + e.getMessage()); } finally { MojoLogAppender.endLogging(); } } /** * Will write the output file, including the wrapper around the code, if any * exist. * * @param outFile * The file to write to * @param compiler * The google compiler * @param wrapper * the string to wrap around the code (using the codePlaceholder) * @param codePlaceholder * the identifier for the code * @throws IOException * when the file cannot be written to. */ static void writeOutput(final File outFile, final Compiler compiler, final String wrapper, final String codePlaceholder) throws IOException { FileWriter out = new FileWriter(outFile); String code = compiler.toSource(); boolean threw = true; try { int pos = wrapper.indexOf(codePlaceholder); LOGGER.debug("wrapper = " + wrapper); if (pos != -1) { String prefix = ""; if (pos > 0) { prefix = wrapper.substring(0, pos); LOGGER.debug("prefix" + prefix); out.append(prefix); } out.append(code); int suffixStart = pos + codePlaceholder.length(); if (suffixStart != wrapper.length()) { LOGGER.debug("suffix" + wrapper.substring(suffixStart)); // Something after placeholder? out.append(wrapper.substring(suffixStart)); } // Make sure we always end output with a line feed. out.append('\n'); // If we have a source map, adjust its offsets to match // the code WITHIN the wrapper. if (compiler != null && compiler.getSourceMap() != null) { compiler.getSourceMap().setWrapperPrefix(prefix); } } else { out.append(code); out.append('\n'); } threw = false; } finally { Closeables.close(out, threw); } } /** * @return the generated assert javascript file */ private File getGeneratedAssertJS() { return new File( JsarRelativeLocations .getAssertDepsLocation(frameworkTargetDirectory), generatedAssertJS); } /** * @return the generated debug javascript file */ private File getGeneratedDebugJS() { return new File( JsarRelativeLocations .getDebugDepsLocation(frameworkTargetDirectory), generatedDebugJS); } /** * @return the generated test javascript file */ private File getGeneratedTestJS() { return new File( JsarRelativeLocations .getTestDepsLocation(frameworkTargetDirectory), generatedAssertJS); } /** * @return the generated assert requires javascript file */ private File getGeneratedAssertRequiresJS() { return new File( JsarRelativeLocations .getAssertRequiresLocation(frameworkTargetDirectory), generatedAssertRequiresJS); } /** * @return the generated debug requires javascript file */ private File getGeneratedDebugRequiresJS() { return new File( JsarRelativeLocations .getDebugRequiresLocation(frameworkTargetDirectory), generatedDebugRequiresJS); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy