
org.bluestemsoftware.open.eoa.plugin.compile.AbstractCompilerMojo Maven / Gradle / Ivy
/**
* Copyright 2008 Bluestem Software LLC. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
package org.bluestemsoftware.open.eoa.plugin.compile;
/*
* Adapted from org.apache.maven.plugin.CompilerMojo which was released under the following
* license:
*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.plugin.MojoExecutionException;
import org.bluestemsoftware.open.eoa.plugin.AbstractDeploymentMojo;
import org.bluestemsoftware.open.eoa.plugin.util.FileFilterImpl;
import org.codehaus.plexus.compiler.Compiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerError;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.manager.CompilerManager;
import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.util.StringUtils;
public abstract class AbstractCompilerMojo extends AbstractDeploymentMojo {
/**
* Indicates whether the build will continue even if there are compilation errors; defaults
* to true.
*
* @parameter expression="${maven.compiler.failOnError}" default-value="true"
*/
protected boolean failOnError = true;
/**
* Set to true to include debugging information in the compiled class files.
*
* @parameter expression="${maven.compiler.debug}" default-value="true"
*/
protected boolean debug = true;
/**
* Set to true to show messages about what the compiler is doing.
*
* @parameter expression="${maven.compiler.verbose}" default-value="false"
*/
protected boolean verbose;
/**
* Sets whether to show source locations where deprecated APIs are used.
*
* @parameter expression="${maven.compiler.showDeprecation}" default-value="false"
*/
protected boolean showDeprecation;
/**
* Set to true to optimize the compiled code using the compiler's optimization methods.
*
* @parameter expression="${maven.compiler.optimize}" default-value="false"
*/
protected boolean optimize;
/**
* Set to true to show compilation warnings.
*
* @parameter expression="${maven.compiler.showWarnings}" default-value="false"
*/
protected boolean showWarnings;
/**
* The -source argument for the Java compiler.
*
* @parameter expression="${maven.compiler.source}" default-value="1.5"
*/
protected String source;
/**
* The -target argument for the Java compiler.
*
* @parameter expression="${maven.compiler.target}" default-value="1.5"
*/
protected String target;
/**
* The -encoding argument for the Java compiler.
*
* @parameter expression="${maven.compiler.encoding}"
*/
protected String encoding;
/**
* Sets the granularity in milliseconds of the last modification date for testing whether a
* source needs recompilation.
*
* @parameter expression="${lastModGranularityMs}" default-value="0"
*/
protected int staleMillis;
/**
* The compiler id of the compiler to use. See this guide for more information.
*
* @parameter expression="${maven.compiler.compilerId}" default-value="javac"
*/
protected String compilerId;
/**
* Version of the compiler to use, ex. "1.3", "1.5", if fork is set to true.
*
* @parameter expression="${maven.compiler.compilerVersion}"
*/
protected String compilerVersion;
/**
* Allows running the compiler in a separate process. If "false" it uses the built in
* compiler, while if "true" it will use an executable.
*
* @parameter default-value="false"
*/
protected boolean fork;
/**
* Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m" if fork is
* set to true.
*
* @parameter expression="${maven.compiler.meminitial}"
*/
protected String meminitial;
/**
* Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m" if
* fork is set to true.
*
* @parameter expression="${maven.compiler.maxmem}"
*/
protected String maxmem;
/**
* Sets the executable of the compiler to use when fork is true.
*
* @parameter expression="${maven.compiler.executable}"
*/
protected String executable;
/**
*
* Sets the arguments to be passed to the compiler (prepending a dash) if fork is set to
* true.
*
*
* This is because the list of valid arguments passed to a Java compiler varies based on
* the compiler version.
*
*
* @parameter
*/
protected Map,?> compilerArguments;
/**
*
* Sets the unformatted argument string to be passed to the compiler if fork is set to
* true.
*
*
* This is because the list of valid arguments passed to a Java compiler varies based on
* the compiler version.
*
*
* @parameter
*/
protected String compilerArgument;
/**
* Sets the name of the output file when compiling a set of sources to a single file.
*
* @parameter expression="${project.build.finalName}"
*/
protected String outputFileName;
/**
* Plexus compiler manager.
*
* @component
*/
protected CompilerManager compilerManager;
/**
* Project test classpath.
*
* @parameter expression="${project.testClasspathElements}"
* @required
* @readonly
*/
protected List> testClasspathElements;
/**
* A list of inclusion filters for the compiler.
*
* @parameter
*/
protected Set testIncludes = new HashSet();
/**
* A list of exclusion filters for the compiler.
*
* @parameter
*/
protected Set testExcludes = new HashSet();
@SuppressWarnings("unchecked")
public void execute() throws MojoExecutionException, CompilationFailureException {
// ----------------------------------------------------------------------
// Look up the compiler. This is done before other code than can
// cause the mojo to return before the lookup is done possibly resulting
// in misconfigured POMs still building.
// ----------------------------------------------------------------------
Compiler compiler;
getLog().debug("Using compiler '" + compilerId + "'.");
try {
compiler = compilerManager.getCompiler(compilerId);
} catch (NoSuchCompilerException e) {
throw new MojoExecutionException("No such compiler '" + e.getCompilerId() + "'.");
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
List compileSourceRoots = removeEmptyCompileSourceRoots(getCompileSourceRoots());
if (compileSourceRoots.isEmpty()) {
getLog().info("No sources to compile");
return;
}
// ----------------------------------------------------------------------
// Create the compiler configuration
// ----------------------------------------------------------------------
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setOutputLocation(getOutputDirectory().getAbsolutePath());
compilerConfiguration.setClasspathEntries(getClasspathElements());
compilerConfiguration.setSourceLocations(compileSourceRoots);
compilerConfiguration.setOptimize(optimize);
compilerConfiguration.setDebug(debug);
compilerConfiguration.setVerbose(verbose);
compilerConfiguration.setShowWarnings(showWarnings);
compilerConfiguration.setShowDeprecation(showDeprecation);
compilerConfiguration.setSourceVersion(source);
compilerConfiguration.setTargetVersion(target);
compilerConfiguration.setSourceEncoding(encoding);
if ((compilerArguments != null) || (compilerArgument != null)) {
LinkedHashMap cplrArgsCopy = new LinkedHashMap();
if (compilerArguments != null) {
for (Iterator> i = compilerArguments.entrySet().iterator(); i.hasNext();) {
Map.Entry me = (Map.Entry)i.next();
String key = (String)me.getKey();
String value = (String)me.getValue();
if (!key.startsWith("-")) {
key = "-" + key;
}
cplrArgsCopy.put(key, value);
}
}
if (!StringUtils.isEmpty(compilerArgument)) {
cplrArgsCopy.put(compilerArgument, null);
}
compilerConfiguration.setCustomCompilerArguments(cplrArgsCopy);
}
compilerConfiguration.setFork(fork);
if (fork) {
if (!StringUtils.isEmpty(meminitial)) {
String value = getMemoryValue(meminitial);
if (value != null) {
compilerConfiguration.setMeminitial(value);
} else {
getLog().info("Invalid value for meminitial '" + meminitial + "'. Ignoring this option.");
}
}
if (!StringUtils.isEmpty(maxmem)) {
String value = getMemoryValue(maxmem);
if (value != null) {
compilerConfiguration.setMaxmem(value);
} else {
getLog().info("Invalid value for maxmem '" + maxmem + "'. Ignoring this option.");
}
}
}
compilerConfiguration.setExecutable(executable);
compilerConfiguration.setWorkingDirectory(basedir);
compilerConfiguration.setCompilerVersion(compilerVersion);
compilerConfiguration.setBuildDirectory(buildDirectory);
compilerConfiguration.setOutputFileName(outputFileName);
// TODO: have an option to always compile (without need to clean)
Set staleSources;
boolean canUpdateTarget;
try {
staleSources = computeStaleSources(compilerConfiguration, compiler,
getSourceInclusionScanner(staleMillis));
canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
if (compiler.getCompilerOutputStyle().equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
&& !canUpdateTarget) {
getLog().info("RESCANNING!");
// TODO: This second scan for source files is sub-optimal
String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
Set sources = computeStaleSources(compilerConfiguration, compiler,
getSourceInclusionScanner(inputFileEnding));
compilerConfiguration.setSourceFiles(sources);
} else {
compilerConfiguration.setSourceFiles(staleSources);
}
} catch (CompilerException e) {
throw new MojoExecutionException("Error while computing stale sources.", e);
}
if (staleSources.isEmpty()) {
getLog().info("Nothing to compile - all classes are up to date");
return;
}
// ----------------------------------------------------------------------
// Compile!
// ----------------------------------------------------------------------
List> messages;
try {
messages = compiler.compile(compilerConfiguration);
} catch (Exception e) {
// TODO: don't catch Exception
throw new MojoExecutionException("Fatal error compiling", e);
}
boolean compilationError = false;
for (Iterator> i = messages.iterator(); i.hasNext();) {
CompilerError message = (CompilerError)i.next();
if (message.isError()) {
compilationError = true;
break;
}
}
if (compilationError && failOnError) {
throw new CompilationFailureException(messages);
} else {
for (Iterator> i = messages.iterator(); i.hasNext();) {
CompilerError message = (CompilerError)i.next();
getLog().warn(message.toString());
}
}
}
protected abstract List> getCompileSourceRoots();
protected abstract File getOutputDirectory();
protected abstract List> getClasspathElements() throws MojoExecutionException;
protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
SourceInclusionScanner scanner = null;
if (testIncludes.isEmpty() && testExcludes.isEmpty()) {
scanner = new StaleSourceScanner(staleMillis);
} else {
if (testIncludes.isEmpty()) {
testIncludes.add("**/*.java");
}
scanner = new StaleSourceScanner(staleMillis, testIncludes, testExcludes);
}
return scanner;
}
protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
SourceInclusionScanner scanner = null;
if (testIncludes.isEmpty() && testExcludes.isEmpty()) {
testIncludes = Collections.singleton("**/*." + inputFileEnding);
scanner = new SimpleSourceInclusionScanner(testIncludes, Collections.EMPTY_SET);
} else {
if (testIncludes.isEmpty()) {
testIncludes.add("**/*." + inputFileEnding);
}
scanner = new SimpleSourceInclusionScanner(testIncludes, testExcludes);
}
return scanner;
}
protected File copyDir(File source, File target) throws MojoExecutionException {
target.mkdirs();
File[] entries = source.listFiles(new FileFilterImpl());
for (int i = 0; i < entries.length; i++) {
File entry = entries[i];
File file = new File(target, File.separator + entry.getName());
if (entry.isDirectory()) {
copyDir(entry, file);
} else {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(entry);
out = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[4096];
for (int length = 0; length >= 0; length = in.read(buffer)) {
out.write(buffer, 0, length);
}
} catch (Exception ex) {
throw new MojoExecutionException("Error copying test resources. " + ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignore) {
}
}
if (out != null) {
try {
out.close();
} catch (IOException ignore) {
}
}
}
}
}
return target;
}
private String getMemoryValue(String setting) {
String value = null;
// Allow '128' or '128m'
if (isDigits(setting)) {
value = setting + "m";
} else {
if ((isDigits(setting.substring(0, setting.length() - 1))) && (setting.toLowerCase().endsWith("m"))) {
value = setting;
}
}
return value;
}
private boolean isDigits(String string) {
for (int i = 0; i < string.length(); i++) {
if (!Character.isDigit(string.charAt(i))) {
return false;
}
}
return true;
}
private Set computeStaleSources(CompilerConfiguration compilerConfiguration, Compiler compiler, SourceInclusionScanner scanner) throws MojoExecutionException, CompilerException {
CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
SourceMapping mapping;
File outputDirectory;
if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
mapping = new SuffixMapping(compiler.getInputFileEnding(compilerConfiguration), compiler
.getOutputFileEnding(compilerConfiguration));
outputDirectory = getOutputDirectory();
} else if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) {
mapping = new SingleTargetSourceMapping(compiler.getInputFileEnding(compilerConfiguration), compiler
.getOutputFile(compilerConfiguration));
outputDirectory = buildDirectory;
} else {
throw new MojoExecutionException("Unknown compiler output style: '" + outputStyle + "'.");
}
scanner.addSourceMapping(mapping);
Set staleSources = new HashSet();
for (Iterator> it = getCompileSourceRoots().iterator(); it.hasNext();) {
String sourceRoot = (String)it.next();
File rootFile = new File(sourceRoot);
if (!rootFile.isDirectory()) {
continue;
}
try {
Iterator> itr = scanner.getIncludedSources(rootFile, outputDirectory).iterator();
while (itr.hasNext()) {
staleSources.add((File)itr.next());
}
} catch (InclusionScanException e) {
throw new MojoExecutionException("Error scanning source root: \'"
+ sourceRoot
+ "\' "
+ "for stale files to recompile.", e);
}
}
return staleSources;
}
/**
* @todo also in ant plugin. This should be resolved at some point so that it does not need
* to be calculated continuously - or should the plugins accept empty source roots as
* is?
*/
private static List removeEmptyCompileSourceRoots(List> compileSourceRootsList) {
List newCompileSourceRootsList = new ArrayList();
if (compileSourceRootsList != null) {
// copy as I may be modifying it
for (Iterator> i = compileSourceRootsList.iterator(); i.hasNext();) {
String srcDir = (String)i.next();
if (!newCompileSourceRootsList.contains(srcDir) && new File(srcDir).exists()) {
newCompileSourceRootsList.add(srcDir);
}
}
}
return newCompileSourceRootsList;
}
}