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

biz.gabrys.maven.plugins.lesscss.CompileMojo Maven / Gradle / Ivy

Go to download

Compiles Less sources to CSS files using extended version of the LessCSS Compiler.

The newest version!
/*
 * LessCSS Maven Plugin
 * http://lesscss-maven-plugin.projects.gabrys.biz/
 *
 * Copyright (c) 2015 Adam Gabryś
 *
 * This file is licensed under the BSD 3-Clause (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain:
 *  - a copy of the License at project page
 *  - a template of the License at https://opensource.org/licenses/BSD-3-Clause
 */
package biz.gabrys.maven.plugins.lesscss;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.AbstractMojo;
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.apache.maven.project.MavenProject;

import biz.gabrys.lesscss.compiler.CompilerOptions;
import biz.gabrys.lesscss.compiler.CompilerOptionsBuilder;
import biz.gabrys.lesscss.extended.compiler.CachingCompiledCodeExtendedCompiler;
import biz.gabrys.lesscss.extended.compiler.CachingSourceCodeExtendedCompilerBuilder;
import biz.gabrys.lesscss.extended.compiler.ExtendedCompiler;
import biz.gabrys.lesscss.extended.compiler.NonCachingExtendedCompilerBuilder;
import biz.gabrys.lesscss.extended.compiler.cache.FullCache;
import biz.gabrys.lesscss.extended.compiler.cache.FullCacheAdapterBuilder;
import biz.gabrys.lesscss.extended.compiler.cache.FullCacheBuilder;
import biz.gabrys.lesscss.extended.compiler.control.expiration.CompiledSourceExpirationChecker;
import biz.gabrys.lesscss.extended.compiler.control.processor.PostCompilationProcessor;
import biz.gabrys.lesscss.extended.compiler.source.LocalSource;
import biz.gabrys.lesscss.extended.compiler.source.SourceFactory;
import biz.gabrys.lesscss.extended.compiler.source.SourceFactoryBuilder;
import biz.gabrys.maven.plugin.util.classpath.ContextClassLoaderExtender;
import biz.gabrys.maven.plugin.util.io.DestinationFileCreator;
import biz.gabrys.maven.plugin.util.io.FileScanner;
import biz.gabrys.maven.plugin.util.io.ScannerFactory;
import biz.gabrys.maven.plugin.util.io.ScannerPatternFormat;
import biz.gabrys.maven.plugin.util.parameter.ParametersLogBuilder;
import biz.gabrys.maven.plugin.util.parameter.converter.ValueToStringConverter;
import biz.gabrys.maven.plugin.util.parameter.sanitizer.LazySimpleSanitizer;
import biz.gabrys.maven.plugin.util.parameter.sanitizer.LazySimpleSanitizer.ValueContainer;
import biz.gabrys.maven.plugin.util.parameter.sanitizer.SimpleSanitizer;
import biz.gabrys.maven.plugin.util.timer.SystemTimer;
import biz.gabrys.maven.plugin.util.timer.Time;
import biz.gabrys.maven.plugin.util.timer.Timer;
import biz.gabrys.maven.plugins.lesscss.compiler.LoggingCompilationDateCache;
import biz.gabrys.maven.plugins.lesscss.compiler.LoggingCompiledCodeCache;
import biz.gabrys.maven.plugins.lesscss.compiler.LoggingCompiler;
import biz.gabrys.maven.plugins.lesscss.compiler.PathInCommentPostProcessor;
import biz.gabrys.maven.plugins.lesscss.compiler.PathInCommentSourceCodeCache;
import biz.gabrys.maven.plugins.lesscss.compiler.PluginCompiler;
import biz.gabrys.maven.plugins.lesscss.compiler.PluginSourceExpirationChecker;

/**
 * Compiles Less files to CSS stylesheets
 * using extended version of the
 * LessCSS Compiler.
 * @since 1.0
 */
@Mojo(name = "compile", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM,
        threadSafe = true)
public class CompileMojo extends AbstractMojo {

    /**
     * Defines whether to skip the plugin execution.
     * @since 1.0
     */
    @Parameter(property = "lesscss.skip", defaultValue = "false")
    protected boolean skip;

    /**
     * Defines whether the plugin runs in verbose mode.
* Notice: always true in debug mode. * @since 1.0 */ @Parameter(property = "lesscss.verbose", defaultValue = "false") protected boolean verbose; /** * Forces the Less compiler to always compile the * Less sources. By default Less sources are * only compiled when modified (including imports) or the CSS stylesheet * does not exist.
* Notice: always false when watch is equal to true. * @since 1.0 */ @Parameter(property = "lesscss.force", defaultValue = "false") protected boolean force; /** * Defines whether the plugin should always overwrite destination files (also if sources did not changed).
* Notice: always true when force is equal to true. * @since 1.0 */ @Parameter(property = "lesscss.alwaysOverwrite", defaultValue = "false") protected boolean alwaysOverwrite; /** * The directory which contains the Less sources. * @since 1.0 */ @Parameter(property = "lesscss.sourceDirectory", defaultValue = "${project.basedir}/src/main/less") protected File sourceDirectory; /** * The directory for compiled CSS stylesheets. * @since 1.0 */ @Parameter(property = "lesscss.outputDirectory", defaultValue = "${project.build.directory}") protected File outputDirectory; /** * Defines inclusion and exclusion fileset patterns format. Available options: *
    *
  • ant - Ant * patterns
  • *
  • regex - regular expressions (use '/' as path separator)
  • *
* @since 1.0 */ @Parameter(property = "lesscss.filesetPatternFormat", defaultValue = "ant") protected String filesetPatternFormat; /** * List of files to include. Specified as fileset patterns whose are relative to the * source directory. See available fileset patterns * formats.
* Default value is: ["**/*.less"] for ant or * ["^.+\.less$"] for regex. * @since 1.0 */ @Parameter protected String[] includes = new String[0]; /** * List of files to exclude. Specified as fileset patterns whose are relative to the * source directory. See available fileset patterns * formats.
* Default value is: []. * @since 1.0 */ @Parameter protected String[] excludes = new String[0]; /** * Defines compiler type used in compilation process. Available options: *
    *
  • full - designed to compile files placed on a local hard drive, in the classpath and in the network *
  • *
  • local - designed to compile files placed on a local hard drive
  • *
* @since 1.0 */ @Parameter(property = "lesscss.compilerType", defaultValue = "full") protected String compilerType; /** * Defines types of dependencies whose will be added to plugin classpath (required for classpath:// * support).
* Notice: ignored when compiler type is not equal to full.
* Default value is: ["jar", "war", "zip"]. * @since 1.2.0 */ @Parameter(property = "lesscss.classpathLoadedDependenciesTypes") protected String[] classpathLoadedDependenciesTypes = new String[0]; /** * Defines whether the plugin should add comments with sources paths at the beginning and end of each source.
* Notice: always false when compiler type is equal to local or * compress is equal to true.
* Notice: you must clear the working directory if you change this parameter. * @since 1.0 */ @Parameter(property = "lesscss.addCommentsWithPaths", defaultValue = "false") protected boolean addCommentsWithPaths; /** * Restricted class name prefix used to create comments with sources paths.
* Notice: you must clear the working directory if you change this parameter. * @since 1.0 */ @Parameter(property = "lesscss.addCommentsWithPathsClassPrefix", defaultValue = "gabrys-biz-comment-with-path-marker-class") protected String addCommentsWithPathsClassPrefix; /** * Whether the compiler should minify the CSS code.
* Notice: you must clear the working directory if you change this parameter * and force is equal to false. * @since 1.0 */ @Parameter(property = "lesscss.compress", defaultValue = "false") protected boolean compress; /** * List of options passed to the compiler. See * Less options
* Notice: you must clear the working directory if you change this parameter. *
* Default value is: []. * @since 1.0 */ @Parameter protected String[] compilerOptions = new String[0]; /** * Sources encoding.
* Notice: you must clear the working directory if you change this parameter * and force is equal to false. * @since 1.0 */ @Parameter(property = "lesscss.encoding", defaultValue = "${project.build.sourceEncoding}") protected String encoding; /** * Destination files naming format. {fileName} is equal to source file name without extension. * @since 1.0 */ @Parameter(property = "lesscss.outputFileFormat", defaultValue = DestinationFileCreator.FILE_NAME_PARAMETER + ".css") protected String outputFileFormat; /** * Defines whether the plugin should watch for changes in source files and compile if it detects any. * @since 1.0 */ @Parameter(property = "lesscss.watch", defaultValue = "false") protected boolean watch; /** * The interval in seconds between the plugin searching for changes in source files.
* Notice: all values smaller than 1 are treated as 1. * @since 1.0 */ @Parameter(property = "lesscss.watchInterval", defaultValue = "5") protected int watchInterval; /** * The plugin working directory. * @since 1.0 */ @Parameter(property = "lesscss.workingDirectory", defaultValue = "${project.build.directory}/gabrys-biz-lesscss-maven-plugin") protected File workingDirectory; @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; private void logParameters() { if (!getLog().isDebugEnabled()) { return; } final ParametersLogBuilder logger = new ParametersLogBuilder(getLog()); logger.append("skip", skip); logger.append("verbose", verbose, new SimpleSanitizer(verbose, Boolean.TRUE)); logger.append("force", force, new SimpleSanitizer(!watch || !force, Boolean.FALSE)); logger.append("alwaysOverwrite", alwaysOverwrite, new SimpleSanitizer(!(!watch && force && !alwaysOverwrite), Boolean.TRUE)); logger.append("sourceDirectory", sourceDirectory); logger.append("outputDirectory", outputDirectory); logger.append("filesetPatternFormat", filesetPatternFormat); logger.append("includes", includes, new LazySimpleSanitizer(includes.length != 0, new ValueContainer() { public Object getValue() { return getDefaultIncludes(); } })); logger.append("excludes", excludes); logger.append("compilerType", compilerType); logger.append("classpathLoadedDependenciesTypes", classpathLoadedDependenciesTypes, new LazySimpleSanitizer(classpathLoadedDependenciesTypes.length != 0, new ValueContainer() { public Object getValue() { return getDefaultClasspathLoadedDependenciesTypes(); } })); logger.append("addCommentsWithPaths", addCommentsWithPaths, new SimpleSanitizer(!compress, Boolean.FALSE)); logger.append("addCommentsWithPathsClassPrefix", addCommentsWithPathsClassPrefix); logger.append("compress", compress); logger.append("compilerOptions", compilerOptions); logger.append("encoding", encoding); logger.append("outputFileFormat", outputFileFormat); logger.append("watch", watch); logger.append("watchInterval", watchInterval, new ValueToStringConverter() { private static final long MILLISECONDS_IN_SECOND = 1000L; public String convert(final Object value) { final Integer number = (Integer) value; final StringBuilder text = new StringBuilder(); text.append(number); if (number > 0) { text.append(" ("); text.append(new Time(number * MILLISECONDS_IN_SECOND)); text.append(')'); } return text.toString(); } }, new SimpleSanitizer(watchInterval > 0, Integer.valueOf(1))); logger.append("workingDirectory", workingDirectory); logger.debug(); } private String[] getDefaultIncludes() { if (ScannerPatternFormat.ANT.name().equalsIgnoreCase(filesetPatternFormat)) { return new String[] { "**/*.less" }; } else { return new String[] { "^.+\\.less$" }; } } private static String[] getDefaultClasspathLoadedDependenciesTypes() { return new String[] { "jar", "war", "zip" }; } private void calculateParameters() { if (getLog().isDebugEnabled()) { verbose = true; } if (watch) { force = false; } if (force) { alwaysOverwrite = true; } if (compress) { addCommentsWithPaths = false; } if (includes.length == 0) { includes = getDefaultIncludes(); } if (classpathLoadedDependenciesTypes.length == 0) { classpathLoadedDependenciesTypes = getDefaultClasspathLoadedDependenciesTypes(); } if (watchInterval < 1) { watchInterval = 1; } } public void execute() throws MojoFailureException { logParameters(); if (skip) { getLog().info("Skips job execution"); return; } calculateParameters(); if ("full".equals(compilerType)) { addDependenciesToClasspath(); } if (watch) { runWatchMode(); } else { runCompilation(); } } private void addDependenciesToClasspath() { if (verbose) { getLog().info("Adding project dependencies to classpath..."); } final ContextClassLoaderExtender extender = new ContextClassLoaderExtender(project, getLog()); extender.addDependencies(classpathLoadedDependenciesTypes); } private void runWatchMode() throws MojoFailureException { getLog().info("Starts watch mode on the " + sourceDirectory); Thread.currentThread().setPriority(Thread.MIN_PRIORITY); final long interval = watchInterval * 1000L; while (true) { runCompilation(); try { Thread.sleep(interval); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); return; } } } private void runCompilation() throws MojoFailureException { if (!sourceDirectory.exists()) { getLog().warn("Source directory does not exist: " + sourceDirectory.getAbsolutePath()); return; } final Collection files = getFiles(); if (files.isEmpty()) { getLog().warn("No sources to compile"); return; } if (force) { deleteWorkingDirectory(); } compileFiles(files); } private Collection getFiles() { final ScannerPatternFormat patternFormat = ScannerPatternFormat.toPattern(filesetPatternFormat); final FileScanner scanner = new ScannerFactory().create(patternFormat, getLog()); if (verbose) { getLog().info("Scanning directory for sources..."); } return scanner.getFiles(sourceDirectory, includes, excludes); } private void deleteWorkingDirectory() throws MojoFailureException { if (!workingDirectory.exists()) { return; } if (getLog().isDebugEnabled()) { getLog().debug("Deleting working directory: " + workingDirectory.getAbsolutePath()); } try { FileUtils.deleteDirectory(workingDirectory); } catch (final IOException e) { throw new MojoFailureException(String.format("Cannot delete working directory: %s", workingDirectory.getAbsolutePath()), e); } } private void compileFiles(final Collection files) throws MojoFailureException { final PluginCompiler compiler = createCompiler(); final CompilerOptions options = createOptions(); final String sourceFilesText = "source" + (files.size() != 1 ? "s" : ""); getLog().info(String.format("Compiling %s %s to %s", files.size(), sourceFilesText, outputDirectory.getAbsolutePath())); final Timer timer = SystemTimer.getStartedTimer(); for (final File file : files) { compileFile(compiler, options, file); } getLog().info(String.format("Finished %s compilation in %s", sourceFilesText, timer.stop())); } private PluginCompiler createCompiler() { if (getLog().isDebugEnabled()) { getLog().debug("Creating compiler..."); } FullCache cache = null; ExtendedCompiler compiler; SourceFactory sourceFactory; if ("full".equalsIgnoreCase(compilerType)) { cache = new FullCacheBuilder().withDirectory(workingDirectory).create(); sourceFactory = new SourceFactoryBuilder().withClasspath().withStandard().create(); PostCompilationProcessor postProcessor = null; if (addCommentsWithPaths) { cache = new FullCacheAdapterBuilder(cache) .withSourceCodeCache(new PathInCommentSourceCodeCache(cache, addCommentsWithPathsClassPrefix)).create(); postProcessor = new PathInCommentPostProcessor(addCommentsWithPathsClassPrefix); } compiler = new CachingSourceCodeExtendedCompilerBuilder(cache).withSourceFactory(sourceFactory).withPostProcessor(postProcessor) .create(); } else if ("local".equalsIgnoreCase(compilerType)) { sourceFactory = force ? null : new SourceFactoryBuilder().withLocal().create(); compiler = new NonCachingExtendedCompilerBuilder().create(); } else { throw new IllegalArgumentException(String.format("Cannot find compiler for type \"%s\"", compilerType)); } if (verbose) { compiler = new LoggingCompiler(compiler, getLog()); } if (!force) { cache = cache != null ? cache : new FullCacheBuilder().withDirectory(workingDirectory).create(); if (verbose) { final FullCacheAdapterBuilder builder = new FullCacheAdapterBuilder(cache); builder.withCompiledCodeCache(new LoggingCompiledCodeCache(cache, getLog())); if (getLog().isDebugEnabled()) { builder.withCompilationDateCache(new LoggingCompilationDateCache(cache, getLog())); } cache = builder.create(); } final CompiledSourceExpirationChecker expirationChecker = new PluginSourceExpirationChecker(cache, sourceFactory, getLog()); compiler = new CachingCompiledCodeExtendedCompiler(compiler, expirationChecker, cache, cache); } return new PluginCompiler(compiler, cache); } private CompilerOptions createOptions() { final CompilerOptionsBuilder builder = new CompilerOptionsBuilder(); builder.setMinified(compress); final CompilerOptions options = builder.create(); final List arguments = new ArrayList(); arguments.addAll(options.getArguments()); arguments.addAll(Arrays.asList(compilerOptions)); return new CompilerOptions(arguments); } private void compileFile(final PluginCompiler compiler, final CompilerOptions options, final File source) throws MojoFailureException { Timer timer = null; if (verbose) { getLog().info("Processing Less source: " + source.getAbsolutePath()); timer = SystemTimer.getStartedTimer(); } final String compiled = compiler.compile(new LocalSource(source, encoding), options); saveCompiledCode(source, compiled, compiler.getCompilationDate()); if (timer != null) { getLog().info("Finished in " + timer.stop()); } } private void saveCompiledCode(final File source, final String compiled, final Date compilationDate) throws MojoFailureException { final File destination = new DestinationFileCreator(sourceDirectory, outputDirectory, outputFileFormat).create(source); final boolean skipsFileSaving = !force && !alwaysOverwrite && destination.exists() && compilationDate.before(new Date(destination.lastModified())); if (skipsFileSaving) { if (verbose) { getLog().info("Skips saving CSS compiled code to file, because cached version is older than destination file: " + destination.getAbsolutePath()); } return; } if (verbose) { getLog().info("Saving CSS code to " + destination.getAbsolutePath()); } try { FileUtils.write(destination, compiled, encoding); } catch (final IOException e) { throw new MojoFailureException(String.format("Cannot save CSS compiled code to file: %s", destination.getAbsolutePath()), e); } } }