
net.jangaroo.jooc.mvnplugin.AbstractCompilerMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jangaroo-maven-plugin Show documentation
Show all versions of jangaroo-maven-plugin Show documentation
This plugin compiles Jangaroo sources to JavaScript.
The newest version!
package net.jangaroo.jooc.mvnplugin;
import com.google.common.collect.Lists;
import net.jangaroo.jooc.AbstractCompileLog;
import net.jangaroo.jooc.Jooc;
import net.jangaroo.jooc.api.CompilationResult;
import net.jangaroo.jooc.config.DebugMode;
import net.jangaroo.jooc.config.JoocConfiguration;
import net.jangaroo.jooc.config.NamespaceConfiguration;
import net.jangaroo.jooc.config.PublicApiViolationsMode;
import net.jangaroo.jooc.config.SemicolonInsertionMode;
import net.jangaroo.jooc.mvnplugin.sencha.SenchaUtils;
import net.jangaroo.jooc.mvnplugin.util.ConversionUtils;
import net.jangaroo.jooc.mvnplugin.util.FileHelper;
import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Super class for mojos compiling Jangaroo sources.
*/
@SuppressWarnings({"UnusedDeclaration", "UnusedPrivateField"})
public abstract class AbstractCompilerMojo extends AbstractJangarooMojo {
private static final String JANGAROO_GROUP_ID = "net.jangaroo";
private static final String EXML_CONFIG_URI_PREFIX = "exml:";
private static final String EXML_MAVEN_PLUGIN_ARTIFACT_ID = "exml-maven-plugin";
protected static final String USED_UNDECLARED_DEPENDENCIES_WARNING = "Used undeclared %sdependencies found:";
protected static final String UNUSED_DEPENDENCIES_KEY = "unusedDependencies";
protected static final String UNDECLARED_DEPENDENCIES_KEY = "undeclaredDependencies";
protected static final String UNUSED_DECLARED_DEPENDENCIES_WARNING = "Unused declared dependencies found:";
/**
* Indicates whether the build will fail if there are compilation errors.
* Defaults to "true".
*/
@SuppressWarnings("FieldCanBeLocal")
@Parameter(property = "maven.compiler.failOnError")
private boolean failOnError = true;
/**
* Indicates whether the build will fail if there are dependency errors discovered.
* Used undeclared dependencies are considered as dependency errors, while declared but unused dependencies are
* just considered as warnings and therefore never result in a build failure.
* Defaults to "true".
*/
@SuppressWarnings("FieldCanBeLocal")
@Parameter(property = "maven.compiler.failOnDependencyError")
private boolean failOnDependencyError = true;
/**
* Skip detecting, reporting and failing on dependency errors (undeclared
* compile dependencies).
* Defaults to "false".
*/
@Parameter(property = "enforcer.skip")
private boolean skipDependencyChecks = false;
/**
* Set "enableAssertions" to "true" in order to generate runtime checks for assert statements.
*/
@Parameter(property = "maven.compile.ea")
private boolean enableAssertions;
/**
* Set "allowDuplicateLocalVariables" to "true" in order to allow multiple declarations of local variables
* within the same scope.
*/
@Parameter(property = "maven.compiler.allowDuplicateLocalVariables")
private boolean allowDuplicateLocalVariables;
/**
* "publicApiViolations" controls how the compiler reacts on usage of non-public API classes,
* i.e. classes annotated with [ExcludeClass]
.
* It can take the values "warn" to log a warning whenever such a class is used, "allow" to suppress such warnings,
* and "error" to stop the build with an error.
*/
@Parameter(property = "maven.compiler.publicApiViolations")
private String publicApiViolations = PublicApiViolationsMode.WARN.toString().toLowerCase();
/**
* If set to "true", the compiler will add an [ExcludeClass] annotation to any
* API stub whose source class contains neither an [PublicApi] nor an [ExcludeClass]
* annotation.
*/
@Parameter(property = "maven.compiler.excludeClassByDefault")
private boolean excludeClassByDefault;
/**
* Let the compiler generate JavaScript source maps that allow debuggers
* (currently only Google Chrome) to show the original ActionScript source code during
* debugging.
* Set to false
to disable this feature to decrease build time and artifact size.
*/
@Parameter(property = "maven.compiler.generateSourceMaps")
private boolean generateSourceMaps;
/**
* If set to "true", the compiler will generate more detailed progress information.
*/
@Parameter(property = "maven.compiler.verbose")
private boolean verbose;
/**
* Sets the granularity in milliseconds of the last modification
* date for testing whether a source needs recompilation.
*/
@Parameter(property = "lastModGranularityMs")
private int staleMillis;
/**
* Keyword list to be appended to the -g command-line switch. Legal values are one of the following keywords: none, lines, or source.
* If debuglevel is not specified, by default, nothing will be appended to -g. If debug is not turned on, this attribute will be ignored.
*/
@Parameter(property = "maven.compiler.debuglevel")
private String debuglevel = DebugMode.SOURCE.toString().toLowerCase();
/**
* Keyword list to be appended to the -autosemicolon command-line switch. Legal values are one of the following keywords: error, warn (default), or quirks.
*/
@Parameter(property = "maven.compiler.autoSemicolon")
private String autoSemicolon = SemicolonInsertionMode.WARN.toString().toLowerCase();
/**
* Output directory for all generated ActionScript3 files to compile.
*/
@Parameter(defaultValue = "${project.build.directory}/generated-sources/joo")
private File generatedSourcesDirectory;
/**
* Directory where to save ActionScript3 files generated from MXML.
* These are not needed for compilation (MXML files are compiled directly to JavaScript),
* but can be kept for your information.
*/
@Parameter
private File keepGeneratedActionScriptDirectory;
/**
* Output directory into which to generate an SWC-compatible catalog.xml generated
* from all namespaces and manifests.
*/
@Parameter(defaultValue = "${project.build.outputDirectory}")
private File catalogOutputDirectory;
/**
* If set to "true", compiler generates TypeScript output instead of JavaScript.
*/
@Parameter(property = "maven.compiler.migrateToTypeScript")
private boolean migrateToTypeScript = false;
/**
* Sets the target source code format features for migrating ActionScript/MXML code to TypeScript,
* implicitly activating migrateToTypescript
when the value is not 0 (zero).
* The value is a combination (addition) of the following feature flags:
*
* - 1 - simplified this-usage-before-super-constructor-call syntax
* - 2 - simplify (as(foo, Foo)).bar to (foo as Foo).bar, i.e. leave out runtime check that would have caused NPE anyway
* - 4 - generate static blocks (requires TypeScript 4.4, to correctly accept forward-references, 4.7)
*
*/
@Parameter(property = "maven.compiler.typeScriptTargetSourceFormatFeatures")
private long typeScriptTargetSourceFormatFeatures = 0L;
/**
* The Ext namespace is stripped from the relative path to the source root.
* Use "." to explicitly set this configuration to an empty namespace.
*/
@Parameter(property = "extNamespace")
private String extNamespace;
@Parameter(property = "extNamespaceRequired")
private boolean extNamespaceRequired;
/**
* The Ext sass namespace is stripped from the relative path inside the sencha/sass/var
* and sencha/sass/src of the source root. If not set (or null) it falls back to {@link #extNamespace}.
* Use "." to explicitly set this configuration to an empty namespace.
*/
@Parameter(property = "extSassNamespace")
private String extSassNamespace;
@Parameter(defaultValue = "${project.basedir}/src/main/sencha")
private File senchaSrcDir;
/**
* If set to "true", compiler generates parameter initializer code that implements
* ECMAScript semantics, which is subtly different from ActionScript 3 semantics.
* In ECMAScript, initializer values are assigned to all 'undefined' arguments.
* In AS3, initializer values are assigned only if you call a method with less arguments.
* An example would be
* function foo(bar: string = "default"): string {
* return bar;
* }
* foo(); // "default" for both AS3 and ECMAScript semantics
* foo(undefined); // 'undefined' in AS3, "default" in ECMAScript semantics
*/
@Parameter(property = "maven.compiler.useEcmaParameterInitializerSemantics")
private boolean useEcmaParameterInitializerSemantics = false;
/**
* If set to "true", compiler generates no ActionScript syntax in comments, resulting
* in "purer" JavaScript code.
*/
@Parameter(property = "maven.compiler.suppressCommentedActionScriptCode")
private boolean suppressCommentedActionScriptCode = false;
/**
* Experimental:
* The configuration can be used to replace the generated npm package name of a Maven module by a different one.
* It defines a list of replacers consisting of a search and a replace field. The search is interpreted as
* a regular pattern matched against the generated npm package name while the replacement is a string which can
* contain tokens (e.g. $1) matching pattern groups. Order is important, the first matching replacer wins.
*/
@Parameter
private List npmPackageNameReplacers = new ArrayList<>();
/**
* The path to the file where the dependency warnings discovered while compiling the sources should be saved.
*/
@Parameter(property = "maven.compiler.dependencyWarningsOutputFile")
private String dependencyWarningsOutputFile = "target/dependencyReports/dependencyWarnings.txt";
protected abstract List getCompileSourceRoots();
protected abstract File getOutputDirectory();
protected File getClassesOutputDirectory() {
return new File(getOutputDirectory(), "src");
}
/**
* TODO: make this configurable via POM!
* Output directory into which compiled property file classes are generated.
* By default, for packaging type jangaroo-app
, the directory
* ${project.build.directory}/app/locale
* is used, for packaging type swc
, it is
* ${project.build.directory}/packages/${package.name}/locale
.
*/
private File getLocalizedOutputDirectory() {
return new File(getOutputDirectory(), SenchaUtils.SENCHA_LOCALE_PATH);
}
public File getGeneratedSourcesDirectory() {
return generatedSourcesDirectory;
}
@Nullable
protected abstract File getApiOutputDirectory();
public boolean isMigrateToTypeScript() {
return migrateToTypeScript;
}
public boolean isSuppressCommentedActionScriptCode() {
return suppressCommentedActionScriptCode;
}
protected File getCatalogOutputDirectory() {
return catalogOutputDirectory;
}
/**
* Runs the compile mojo
*
* @throws MojoExecutionException
* @throws MojoFailureException
*/
public void execute() throws MojoExecutionException, MojoFailureException {
final Log log = getLog();
if (extNamespace == null) {
if (extNamespaceRequired) {
throw new MojoExecutionException("Flag 'extNamespaceRequired' was enabled but no 'extNamespace' was provided.");
}
extNamespace = "";
}
if (".".equals(extNamespace)) {
extNamespace = "";
}
if (".".equals(extSassNamespace)) {
extSassNamespace = "";
}
if (getCompileSourceRoots().isEmpty()) {
log.info("No sources to compile");
return;
}
// ----------------------------------------------------------------------
// Create the compiler configuration
// ----------------------------------------------------------------------
JoocConfiguration configuration = createJoocConfiguration(log);
if (configuration == null) {
return;
}
Jooc jooc = new Jooc(configuration, new AbstractCompileLog() {
@Override
protected void doLogError(String msg) {
log.error(msg);
}
@Override
public void warning(String msg) {
log.warn(msg);
}
});
int result = compile(jooc);
if (configuration.getDependencyReportOutputFile() == null && !failOnDependencyError) {
log.warn("No directory for dependency warnings specified, ignoring dependency warnings.");
} else {
File dependencyWarningsFile = new File(configuration.getDependencyReportOutputFile());
if (dependencyWarningsFile.exists()) {
try {
Map dependencyWarnings = SenchaUtils.getObjectMapper().readValue(FileUtils.readFileToString(dependencyWarningsFile), Map.class);
printDependencyWarnings(configuration, dependencyWarnings);
List undeclaredDependencies = (List) dependencyWarnings.get(UNDECLARED_DEPENDENCIES_KEY);
if (failOnDependencyError && undeclaredDependencies != null && !undeclaredDependencies.isEmpty()) {
throw new MojoExecutionException("There were dependency errors detected, compilation failed. " +
"Fix the dependency errors or disable fail on dependency errors by setting the \"failOnDependencyError\"-option to \"false\".");
}
} catch (IOException e) {
getLog().error(String.format("There was an error while reading file %s", dependencyWarningsFile.getPath()));
} finally {
new File(configuration.getDependencyReportOutputFile()).delete();
}
}
}
if ((result != CompilationResult.RESULT_CODE_OK) && failOnError) {
log.info("-------------------------------------------------------------");
if (result == CompilationResult.RESULT_CODE_COMPILATION_FAILED) {
log.error("There were compile errors (see log above).");
} else {
log.error("Internal Jangaroo compiler error: " + result + "\nSee log for error details.");
}
log.info("-------------------------------------------------------------");
throw new MojoFailureException("Compilation failed");
}
}
protected JoocConfiguration createJoocConfiguration(Log log) throws MojoExecutionException, MojoFailureException {
JoocConfiguration configuration = new JoocConfiguration();
configuration.setEnableAssertions(enableAssertions);
configuration.setAllowDuplicateLocalVariables(allowDuplicateLocalVariables);
configuration.setVerbose(verbose);
configuration.setExcludeClassByDefault(excludeClassByDefault);
configuration.setGenerateSourceMaps(generateSourceMaps);
configuration.setKeepGeneratedActionScriptDirectory(keepGeneratedActionScriptDirectory);
configuration.setMigrateToTypeScript(migrateToTypeScript);
configuration.setTypeScriptTargetSourceFormatFeatures(typeScriptTargetSourceFormatFeatures);
configuration.setExtNamespace(extNamespace);
configuration.setExtSassNamespace(extSassNamespace != null ? extSassNamespace : extNamespace);
configuration.setUseEcmaParameterInitializerSemantics(useEcmaParameterInitializerSemantics);
configuration.setSuppressCommentedActionScriptCode(suppressCommentedActionScriptCode);
configuration.setNpmPackageName(
ConversionUtils.getNpmPackageName(
getProject().getGroupId(),
getProject().getArtifactId(),
ConversionUtils.getSearchAndReplace(npmPackageNameReplacers)
)
);
if (StringUtils.isNotEmpty(debuglevel)) {
try {
configuration.setDebugMode(DebugMode.valueOf(debuglevel.toUpperCase()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The specified debug level: '" + debuglevel
+ "' is unsupported. " + "Legal values are 'none', 'lines', and 'source'.");
}
}
if (StringUtils.isNotEmpty(autoSemicolon)) {
try {
configuration.setSemicolonInsertionMode(SemicolonInsertionMode.valueOf(autoSemicolon.toUpperCase()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The specified semicolon insertion mode: '" + autoSemicolon
+ "' is unsupported. " + "Legal values are 'error', 'warn', and 'quirks'.");
}
}
if (StringUtils.isNotEmpty(publicApiViolations)) {
try {
configuration.setPublicApiViolationsMode(PublicApiViolationsMode.valueOf(publicApiViolations.toUpperCase()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The specified public API violations mode: '" + publicApiViolations
+ "' is unsupported. " + "Legal values are 'error', 'warn', and 'allow'.");
}
}
HashSet sources = new HashSet<>();
log.debug("starting source inclusion scanner");
sources.addAll(computeStaleSources(staleMillis));
// SASS files
Map sassSourcePathByType = new HashMap<>();
Map sassOutputDirectoryByType = new HashMap<>();
Map> sassSourceFilesByType = new HashMap<>();
for (String sassType : Lists.newArrayList("var", "src")) {
File sassSourceFolder = senchaSrcDir.toPath().resolve("sass").resolve(sassType).toFile();
sassSourcePathByType.put(sassType, sassSourceFolder);
sassSourceFilesByType.put(sassType, listFilesRecursive(sassSourceFolder));
sassOutputDirectoryByType.put(sassType, getOutputDirectory().toPath().resolve("sass").resolve(sassType).toFile());
}
if (sources.isEmpty() && sassSourceFilesByType.values().stream().mapToInt(List::size).sum() == 0) {
log.info("Nothing to compile - all classes are up to date");
return null;
}
configuration.setSourceFiles(new ArrayList<>(sources));
try {
configuration.setSourcePath(getCompileSourceRoots());
} catch (IOException e) {
throw new MojoFailureException("could not canonicalize source paths: " + getCompileSourceRoots(), e);
}
configuration.setClassPath(getActionScriptClassPath());
// not setting a compile path lets the compiler skip dependency checks:
if (!skipDependencyChecks) {
configuration.setCompilePath(getActionScriptCompilePath());
}
configuration.setOutputDirectory(getClassesOutputDirectory());
configuration.setLocalizedOutputDirectory(getLocalizedOutputDirectory());
configuration.setApiOutputDirectory(getApiOutputDirectory());
configuration.setFindUnusedDependencies(findUnusedDependencies(staleMillis));
configuration.setDependencyReportOutputFile(dependencyWarningsOutputFile);
configuration.setSassSourceFilesByType(sassSourceFilesByType);
try {
configuration.setSassSourcePathByType(sassSourcePathByType);
} catch (IOException e) {
throw new MojoFailureException("could not canonicalize sass source paths: " + sassSourcePathByType, e);
}
try {
configuration.setSassOutputDirectoryByType(sassOutputDirectoryByType);
} catch (IOException e) {
throw new MojoFailureException("could not canonicalize sass output directories: " + sassOutputDirectoryByType, e);
}
List allNamespaces = new ArrayList<>();
if (getNamespaces() != null) {
allNamespaces.addAll(Arrays.asList(getNamespaces()));
}
String configClassPackage = findConfigClassPackageInExmlPluginConfiguration();
if (configClassPackage != null) {
String namespace = EXML_CONFIG_URI_PREFIX + configClassPackage;
getLog().info(String.format("Adding namespace %s derived from %s configuration.",
namespace, EXML_MAVEN_PLUGIN_ARTIFACT_ID));
allNamespaces.add(new NamespaceConfiguration(namespace, null));
}
configuration.setNamespaces(allNamespaces);
if (log.isDebugEnabled()) {
log.debug("Source path: " + configuration.getSourcePath().toString().replace(',', '\n'));
log.debug("Class path: " + configuration.getClassPath().toString().replace(',', '\n'));
log.debug("Output directory: " + configuration.getOutputDirectory());
if (configuration.getApiOutputDirectory() != null) {
log.debug("API output directory: " + configuration.getApiOutputDirectory());
}
}
return configuration;
}
protected boolean findUnusedDependencies(int staleMillis) {
return staleMillis < 0 || !getClassesOutputDirectory().exists();
}
private String findConfigClassPackageInExmlPluginConfiguration() {
String configClassPackage = null;
@SuppressWarnings("unchecked")
List plugins = getProject().getBuildPlugins();
for (Plugin plugin : plugins) {
if (JANGAROO_GROUP_ID.equals(plugin.getGroupId()) && EXML_MAVEN_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) {
Object exmlPluginConfiguration = plugin.getConfiguration();
if (exmlPluginConfiguration instanceof Xpp3Dom) {
Xpp3Dom configClassPackageElement = ((Xpp3Dom) exmlPluginConfiguration).getChild("configClassPackage");
if (configClassPackageElement != null) {
configClassPackage = configClassPackageElement.getValue();
if (configClassPackage != null) {
configClassPackage = configClassPackage.trim();
if (!configClassPackage.isEmpty()) {
break;
}
}
}
}
}
}
return configClassPackage;
}
protected abstract List getActionScriptCompilePath();
protected abstract List getActionScriptClassPath();
protected abstract void printDependencyWarnings(JoocConfiguration joocConfiguration, Map dependencyWarnings);
protected void printUnusedDependencyWarnings(JoocConfiguration joocConfiguration, List unusedDependencies) {
if (joocConfiguration.isFindUnusedDependencies() && !unusedDependencies.isEmpty()) {
getLog().warn(UNUSED_DECLARED_DEPENDENCIES_WARNING);
for (String unusedDeclaredDependency : unusedDependencies) {
resolveDependency(unusedDeclaredDependency).ifPresent(dependency -> getLog().warn(" " + formatDependency(dependency)));
}
}
}
private String formatDependency(Artifact dependency) {
return String.format("%s:%s:%s:%s:%s", dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), dependency.getVersion(), dependency.getScope());
}
protected void printUndeclaredDependencyWarnings(JoocConfiguration joocConfiguration, List undeclaredDependencies, String dependencyNamePrefix) {
if (!undeclaredDependencies.isEmpty()) {
getLog().error(String.format(USED_UNDECLARED_DEPENDENCIES_WARNING, dependencyNamePrefix));
undeclaredDependencies.forEach(dependency -> {
Optional resolvedDependency = resolveDependency(dependency);
if (resolvedDependency.isPresent()) {
getLog().error(formatDependency(resolvedDependency.get()));
List lines = new ArrayList<>(createUsedUndeclaredDependencyWarning(resolvedDependency.get()));
getLog().error("Add the following to your pom:\n" + String.join("\n", lines));
}
});
}
}
protected Optional resolveDependency(String combinedDependencyName) {
return getProject().getArtifacts().stream()
.filter(artifact -> artifact.getFile().getPath().equals(combinedDependencyName))
.distinct()
.findFirst();
}
protected abstract List createUsedUndeclaredDependencyWarning(Artifact dependency);
private int compile(Jooc jooc) throws MojoExecutionException {
File outputDirectory = jooc.getConfig().getOutputDirectory();
FileHelper.ensureDirectory(outputDirectory);
// create api output directory if it does not exist
File apiOutputDirectory = getApiOutputDirectory();
if (apiOutputDirectory != null) {
FileHelper.ensureDirectory(apiOutputDirectory);
}
final List sources = jooc.getConfig().getSourceFiles();
final Log log = getLog();
log.info("Compiling " + sources.size() +
" joo source file"
+ (sources.size() == 1 ? "" : "s")
+ " to " + outputDirectory);
return jooc.run().getResultCode();
}
private List computeStaleSources(int staleMillis) throws MojoExecutionException {
File outputDirectory = getApiOutputDirectory();
String outputFileSuffix = Jooc.AS_SUFFIX;
if (outputDirectory == null) {
outputDirectory = getClassesOutputDirectory();
outputFileSuffix = Jooc.getOutputSuffix(isMigrateToTypeScript());
}
List compileSourceRoots = getCompileSourceRoots();
List staleFiles = new ArrayList<>();
staleFiles.addAll(getMavenPluginHelper().computeStaleSources(compileSourceRoots, getIncludes(), getExcludes(), outputDirectory, Jooc.AS_SUFFIX, outputFileSuffix, staleMillis));
staleFiles.addAll(getMavenPluginHelper().computeStaleSources(compileSourceRoots, getIncludes(), getExcludes(), outputDirectory, Jooc.MXML_SUFFIX, outputFileSuffix, staleMillis));
staleFiles.addAll(getMavenPluginHelper().computeStalePropertiesSources(compileSourceRoots, getIncludes(), getExcludes(), getLocalizedOutputDirectory(), staleMillis));
return staleFiles;
}
private static List listFilesRecursive(File folder) throws MojoExecutionException {
List recursiveFolderContents = new ArrayList<>();
if (!folder.exists()) {
return recursiveFolderContents;
}
if (!folder.isDirectory()) {
throw new MojoExecutionException("Not a directory: " + folder);
}
File[] folderContents = folder.listFiles();
if (folderContents == null) {
throw new MojoExecutionException("Could not read directory: " + folder);
}
for (File file : folderContents) {
if (file.isDirectory()) {
recursiveFolderContents.addAll(listFilesRecursive(file));
} else {
recursiveFolderContents.add(file);
}
}
return recursiveFolderContents;
}
protected abstract Set getIncludes();
protected abstract Set getExcludes();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy