biz.gabrys.maven.plugins.lesscss.CompileMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lesscss-maven-plugin
Show all versions of lesscss-maven-plugin
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:
*
* @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