com.github.cleydyr.maven.plugin.CompileSassMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dart-sass-maven-plugin Show documentation
Show all versions of dart-sass-maven-plugin Show documentation
A Maven plugin that allows to compile SASS using Dart Sass
package com.github.cleydyr.maven.plugin;
import com.github.cleydyr.dart.command.SassCommand;
import com.github.cleydyr.dart.command.builder.SassCommandBuilder;
import com.github.cleydyr.dart.command.enums.SourceMapURLs;
import com.github.cleydyr.dart.command.enums.Style;
import com.github.cleydyr.dart.command.exception.SassCommandException;
import com.github.cleydyr.dart.command.factory.SassCommandBuilderFactory;
import com.github.cleydyr.dart.command.files.FileCounter;
import com.github.cleydyr.dart.command.files.FileCounterException;
import com.github.cleydyr.dart.net.GithubLatestVersionProvider;
import com.github.cleydyr.dart.release.DartSassReleaseParameter;
import com.github.cleydyr.dart.system.OSDetector;
import com.github.cleydyr.dart.system.io.DartSassExecutableExtractor;
import com.github.cleydyr.dart.system.io.DefaultCachedFilesDirectoryProviderFactory;
import com.github.cleydyr.dart.system.io.factory.DartSassExecutableExtractorFactory;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.function.Supplier;
import javax.inject.Inject;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
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.settings.MavenSettingsBuilder;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Goal that compiles a set of sass/scss files from an input directory to an output directory.
*/
@SuppressWarnings("deprecation")
@Mojo(name = "compile-sass", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, threadSafe = true)
public class CompileSassMojo extends AbstractMojo {
private FileCounter fileCounter;
protected SassCommandBuilder sassCommandBuilder;
protected DartSassExecutableExtractorFactory dartSassExecutableExtractorFactory;
protected GithubLatestVersionProvider githubLatestVersionProvider;
protected Supplier cachedFilesDirectoryProvider;
private DartSassReleaseParameter dartSassReleaseParameter;
private URL proxyHost;
private static final Object LOCK = new Object();
@Inject
public CompileSassMojo(
FileCounter fileCounter,
SassCommandBuilderFactory sassCommandBuilderFactory,
DartSassExecutableExtractorFactory dartSassExecutableExtractorFactory,
GithubLatestVersionProvider githubLatestVersionProvider,
DefaultCachedFilesDirectoryProviderFactory cachedFilesDirectoryProviderFactory,
MavenSettingsBuilder mavenSettingsBuilder) {
this.fileCounter = fileCounter;
this.sassCommandBuilder = sassCommandBuilderFactory.getCommanderBuilder();
this.dartSassExecutableExtractorFactory = dartSassExecutableExtractorFactory;
this.githubLatestVersionProvider = githubLatestVersionProvider;
this.cachedFilesDirectoryProvider = cachedFilesDirectoryProviderFactory.get();
if (mavenSettingsBuilder == null) {
return;
}
try {
Settings settings = mavenSettingsBuilder.buildSettings();
Proxy activeProxy = settings.getActiveProxy();
if (activeProxy == null) {
return;
}
String host = activeProxy.getHost();
int port = activeProxy.getPort();
String protocol = activeProxy.getProtocol();
proxyHost = new URL(protocol + "://" + host + ":" + port);
} catch (IOException | XmlPullParserException e) {
getLog().warn("Error while parsing maven settings. Settings like proxy will be ignored.");
}
}
/**
* Path to the folder where the sass/scss are located.
*/
@Parameter(defaultValue = "src/main/sass")
private File inputFolder;
/**
* Path to the folder where the css and source map files will be created.
*/
@Parameter(property = "project.build.directory")
private File outputFolder;
/**
* Paths from where additional load path for Sass to look for stylesheets will be loaded. It can
* be passed multiple times to provide multiple load paths. Earlier load paths will take
* precedence over later ones.
*/
@Parameter
private List loadPaths;
/**
* This option controls the output style of the resulting CSS. Dart Sass supports two output
* styles:
*
* - expanded (the default), which writes each selector and declaration on its own line; and
*
* - compressed, which removes as many extra characters as possible, and writes the entire
* stylesheet on a single line.
*
* Use either EXPANDED
or COMPRESSED
in the plugin configuration.
*/
@Parameter(defaultValue = "EXPANDED")
private Style style;
/**
* This flag tells Sass never to emit a @charset
declaration or a UTF-8 byte-order
* mark. By default, or if the charset
flag is activated, Sass will insert either a
* @charset
declaration (in expanded output mode) or a byte-order mark (in
* compressed output mode) if the stylesheet contains any non-ASCII characters.
*/
@Parameter(defaultValue = "false")
private boolean noCharset;
/**
* This flag tells Sass whether to emit a CSS file when an error occurs during compilation.
* This CSS file describes the error in a comment and in the content property of
* body::before,
so that you can see the error message in the browser without needing to
* switch back to the terminal.
* By default, error CSS is enabled if you’re compiling to at least one file on disk (as opposed
* to standard output). You can activate errorCSS
explicitly to enable it even when
* you’re compiling to standard out (not supported by this Maven plugin), or set it explicitly
* to false
to disable it everywhere. When it’s disabled, the update
* flag and watch
flag (the latter being not yet supported by this Maven plugin)
* will delete CSS files instead when an error occurs.
*/
@Parameter(defaultValue = "true")
private boolean errorCSS;
/**
* If the this flag is set to true
, Sass will only compile stylesheets whose
* dependencies have been modified more recently than the corresponding CSS file was generated.
* It will also print status messages when updating stylesheets.
*/
@Parameter(defaultValue = "false")
private boolean update;
/**
* If the noSourceMap
flag is set to true
, Sass won’t generate any
* source maps. It cannot be passed along with other source map options (namely
* sourceMapURLs
, embedSources
and embedSourceMap
*/
@Parameter(defaultValue = "false")
private boolean noSourceMap;
/**
* This option controls how the source maps that Sass generates link back to the Sass files that
* contributed to the generated CSS. Dart Sass supports two types of URLs:
*
* - relative (the default) uses relative URLs from the location of the source map file to
* the locations of the Sass source file;
and
* - absolute uses the absolute file: URLs of the Sass source files. Note that absolute URLs
* will only work on the same computer that the CSS was compiled.
*
* Use either RELATIVE
or ABSOLUTE
in the plugin configuration.
*/
@Parameter(defaultValue = "RELATIVE")
private SourceMapURLs sourceMapURLs;
/**
* This flag tells Sass to embed the entire contents of the Sass files that contributed to the
* generated CSS in the source map. This may produce very large source maps, but it guarantees
* that the source will be available on any computer no matter how the CSS is served.
*/
@Parameter(defaultValue = "false")
private boolean embedSources;
/**
* This flag tells Sass to embed the contents of the source map file in the generated CSS,
* rather than creating a separate file and linking to it from the CSS.
*/
@Parameter(defaultValue = "false")
private boolean embedSourceMap;
/**
* This flag tells Sass to stop compiling immediately when an error is detected, rather than
* trying to compile other Sass files that may not contain errors. It’s mostly useful in
* many-to-many mode (which is the mode currently supported by this Maven plugin).
*/
@Parameter(defaultValue = "false")
private boolean stopOnError;
/**
* This flag tells Sass to emit terminal colors. By default, it will emit colors if it looks
* like it’s being run on a terminal that supports them. Set it to false
to tell
* Sass to not emit colors.
*/
@Parameter(defaultValue = "true")
private boolean color;
/**
* This flag tells Sass only to emit ASCII characters to the terminal as part of error messages.
* By default, Sass will emit non-ASCII characters for these messages. This flag does not affect
* the CSS output.
*/
@Parameter(defaultValue = "false")
private boolean noUnicode;
/**
* This flag tells Sass not to emit any warnings when compiling. By default, Sass emits warnings
* when deprecated features are used or when the @warn
rule is encountered. It also
* silences the @debug
rule.
*/
@Parameter(defaultValue = "false")
private boolean quiet;
/**
* This flag tells Sass not to emit deprecation warnings that come from dependencies. It
* considers any file that’s transitively imported through a load path to be a “dependency”.
* This flag doesn’t affect the @warn
rule or the @debug
rule.
*/
@Parameter(defaultValue = "false")
private boolean quietDeps;
/**
* This flag tells Sass to print the full Dart stack trace when an error is encountered. It’s
* used by the Sass team for debugging errors.
*/
@Parameter(defaultValue = "false")
private boolean trace;
/**
* This parameter represents the Dart Sass version that should be used to compile Sass files.
* If left unset, the version available at https://github.com/sass/dart-sass/releases/latest
* will be used.
*/
@Parameter
private String version;
/**
* This parameter represents the Dart Sass architecture that should be used to compile Sass
* files. If letf unset, it will be autodetected by the plugin. Accepted values are
* "x64", "aarch32", "aarch64" and "ia32".
*/
@Parameter
private String arch;
/**
* This parameter represents the Dart Sass operating system that should be used to compile
* Sass files. If letf unset, it will be autodetected by the plugin. Accepted values are
* "linux", "macos" and "windows".
*/
@Parameter
private String os;
/**
* This parameter represents a path in the local file system where the release archive
* downloaded from the internet will stored. If letf unset, it will default to
*
* $HOME/.cache/dart-sass-maven-plugin/
on *nix operating systems; or
* %LOCALAPPDATA%\dart-sass-maven-plugin\Cache
on Windows operating systems.
*
*/
@Parameter
private File cachedFilesDirectory;
public void execute() throws MojoExecutionException {
validateProxyHostSyntax();
verifyDefaultParameters();
unsetIncompatibleOptions();
synchronized (LOCK) {
extractExecutable();
}
SassCommand sassCommand = buildSassCommand();
try {
Instant start = Instant.now();
sassCommand.execute();
Instant finish = Instant.now();
long elapsedTime = Duration.between(start, finish).toMillis();
try {
long fileCount = fileCounter.getProcessableFileCount(inputFolder.toPath(), outputFolder.toPath());
getLog().info("Compiled " + fileCount + " files in " + elapsedTime + " ms");
} catch (FileCounterException fileCounterException) {
throw new MojoExecutionException("Error while obtaining file count: ", fileCounterException);
}
} catch (SassCommandException sassCommandException) {
throw new MojoExecutionException("Can't execute SASS command", sassCommandException);
}
}
protected void verifyDefaultParameters() throws MojoExecutionException {
if (os == null) {
os = OSDetector.getOSName();
getLog().info("Auto-detected operating system: " + os);
} else if (!OSDetector.isAcceptedOSName(os)) {
getLog().warn("os value " + os + " is not among the accepted values: "
+ OSDetector.ACCEPTED_OSES.toString());
}
if (arch == null) {
arch = OSDetector.getOSArchitecture();
getLog().info("Auto-detected operating system architecture: " + arch);
} else if (!OSDetector.isAcceptedArchitecture(arch)) {
getLog().warn("architecture value " + arch + " is not among the accepted values: "
+ OSDetector.ACCEPTED_ARCHITECTURES.toString());
}
if (version == null) {
version = githubLatestVersionProvider.get();
getLog().info("Auto-detected latest version: " + version);
}
if (cachedFilesDirectory == null) {
cachedFilesDirectory = cachedFilesDirectoryProvider.get();
getLog().info("Auto-detected cached files directory: " + cachedFilesDirectory);
}
dartSassReleaseParameter = new DartSassReleaseParameter(os, arch, version);
}
public void unsetIncompatibleOptions() {
if (noSourceMap) {
sourceMapURLs = null;
embedSourceMap = false;
embedSources = false;
}
}
public void extractExecutable() throws MojoExecutionException {
DartSassExecutableExtractor dartSassExecutableExtractor =
dartSassExecutableExtractorFactory.getDartSassExecutableExtractor(
dartSassReleaseParameter, cachedFilesDirectory, proxyHost);
try {
dartSassExecutableExtractor.extract();
} catch (Exception exception) {
throw new MojoExecutionException("Unable to extract sass executable", exception);
}
}
private void validateProxyHostSyntax() throws MojoExecutionException {
if (proxyHost == null) {
return;
}
try {
proxyHost.toURI();
} catch (URISyntaxException e) {
throw new MojoExecutionException(e);
}
}
protected SassCommand buildSassCommand() throws MojoExecutionException {
if (loadPaths != null) {
for (File loadPath : loadPaths) {
sassCommandBuilder.withLoadPath(loadPath.toPath());
}
}
setOptions();
Path inputFolderPath = inputFolder.toPath();
sassCommandBuilder.withPaths(inputFolderPath, outputFolder.toPath());
try {
return sassCommandBuilder.build(dartSassReleaseParameter);
} catch (SassCommandException e) {
throw new MojoExecutionException(e);
}
}
protected void setOptions() {
sassCommandBuilder.withStyle(style);
sassCommandBuilder.withNoCharset(noCharset);
sassCommandBuilder.withErrorCSS(errorCSS);
sassCommandBuilder.withUpdate(update);
sassCommandBuilder.withNoSourceMap(noSourceMap);
sassCommandBuilder.withSourceMapURLs(sourceMapURLs);
sassCommandBuilder.withEmbedSources(embedSources);
sassCommandBuilder.withEmbedSourceMap(embedSourceMap);
sassCommandBuilder.withStopOnError(stopOnError);
sassCommandBuilder.withColor(color);
sassCommandBuilder.withNoUnicode(noUnicode);
sassCommandBuilder.withQuiet(quiet);
sassCommandBuilder.withQuietDeps(quietDeps);
sassCommandBuilder.withTrace(trace);
}
public File getInputFolder() {
return inputFolder;
}
public void setInputFolder(File inputFolder) {
this.inputFolder = inputFolder;
}
public File getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(File outputFolder) {
this.outputFolder = outputFolder;
}
public List getLoadPaths() {
return loadPaths;
}
public void setLoadPaths(List loadPaths) {
this.loadPaths = loadPaths;
}
public Style getStyle() {
return style;
}
public void setStyle(Style style) {
this.style = style;
}
public boolean isNoCharset() {
return noCharset;
}
public void setNoCharset(boolean noCharset) {
this.noCharset = noCharset;
}
public boolean isErrorCSS() {
return errorCSS;
}
public void setErrorCSS(boolean errorCSS) {
this.errorCSS = errorCSS;
}
public boolean isUpdate() {
return update;
}
public void setUpdate(boolean update) {
this.update = update;
}
public boolean isNoSourceMap() {
return noSourceMap;
}
public void setNoSourceMap(boolean noSourceMap) {
this.noSourceMap = noSourceMap;
}
public SourceMapURLs getSourceMapURLs() {
return sourceMapURLs;
}
public void setSourceMapURLs(SourceMapURLs sourceMapURLs) {
this.sourceMapURLs = sourceMapURLs;
}
public boolean isEmbedSources() {
return embedSources;
}
public void setEmbedSources(boolean embedSources) {
this.embedSources = embedSources;
}
public boolean isEmbedSourceMap() {
return embedSourceMap;
}
public void setEmbedSourceMap(boolean embedSourceMap) {
this.embedSourceMap = embedSourceMap;
}
public boolean isStopOnError() {
return stopOnError;
}
public void setStopOnError(boolean stopOnError) {
this.stopOnError = stopOnError;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public boolean isNoUnicode() {
return noUnicode;
}
public void setNoUnicode(boolean noUnicode) {
this.noUnicode = noUnicode;
}
public boolean isQuiet() {
return quiet;
}
public void setQuiet(boolean quiet) {
this.quiet = quiet;
}
public boolean isQuietDeps() {
return quietDeps;
}
public void setQuietDeps(boolean quietDeps) {
this.quietDeps = quietDeps;
}
public boolean isTrace() {
return trace;
}
public void setTrace(boolean trace) {
this.trace = trace;
}
}