/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* 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.
*/
package io.sarl.maven.compiler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import javax.inject.Provider;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import org.apache.log4j.Logger;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.io.DirectoryScanner;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.apache.maven.toolchain.ToolchainPrivate;
import org.apache.maven.toolchain.java.JavaToolChain;
import org.arakhne.afc.vmutil.locale.Locale;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.xtext.xbase.lib.util.ReflectExtensions;
import io.sarl.lang.SARLStandaloneSetup;
import io.sarl.lang.compiler.batch.SarlBatchCompiler;
import io.sarl.maven.compiler.MavenLogger.MavenLoggerFactory;
/** Abstract mojo that is able to use the SARL batch compiler.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public abstract class AbstractSarlBatchCompilerMojo extends AbstractSarlMojo {
private Injector injector;
private Provider<SarlBatchCompiler> sarlBatchCompilerProvider;
private ReflectExtensions reflect;
@Component
private ToolchainManager toolchainManager;
@Parameter(readonly = true, defaultValue = "${basedir}/.settings/io.sarl.lang.SARL.prefs")
private String propertiesFileLocation;
@Override
public void prepareExecution() throws MojoExecutionException {
if (this.injector == null) {
this.injector = SARLStandaloneSetup.doSetup();
}
if (this.sarlBatchCompilerProvider == null) {
this.sarlBatchCompilerProvider = this.injector.getProvider(SarlBatchCompiler.class);
}
if (this.reflect == null) {
this.reflect = this.injector.getInstance(ReflectExtensions.class);
}
if (this.sarlBatchCompilerProvider == null || this.reflect == null) {
throw new MojoExecutionException(Locale.getString(AbstractSarlBatchCompilerMojo.class, "INJECTION_ERROR")); //$NON-NLS-1$
}
}
/** Replies the batch compiler for SARL.
*
* @return the batch compiler.
*/
protected SarlBatchCompiler getBatchCompiler() {
return this.sarlBatchCompilerProvider.get();
}
/** Replies the current project.
*
* @return the current project.
*/
protected MavenProject getProject() {
return this.mavenHelper.getSession().getCurrentProject();
}
/** Replies the version of the source.
*
* @return the source version.
*/
protected abstract String getSourceVersion();
/** Replies the encoding of the source.
*
* @return the encoding.
*/
protected abstract String getEncoding();
/** Replies if the Java compiler must be called at post-running stage.
*
* @return <code>true</code> for running the java compiler.
*/
protected abstract boolean getPostRunningOfJavaCompiler();
/** Replies if the inline annotations must be generated by the SARL compiler.
*
* @return <code>true</code> for generating the inline annotations.
*/
protected abstract boolean getGenerateInlines();
/** Replies if the trace files must be generated by the SARL compiler.
*
* @return <code>true</code> for generating the trace files.
*/
protected abstract boolean getGenerateTraceFiles();
/** Replies if the storage files must be generated by the SARL compiler.
*
* @return <code>true</code> for generating the storage files.
*/
protected abstract boolean getGenerateStorageFiles();
/** Run compilation.
*
* @param classPath the classpath
* @param sourcePaths the source paths.
* @param outputPath the output path.
* @throws MojoExecutionException if error.
* @throws MojoFailureException if failure.
*/
protected void compile(List<File> classPath, List<File> sourcePaths, File outputPath) throws MojoExecutionException, MojoFailureException {
final SarlBatchCompiler compiler = getBatchCompiler();
final MavenProject project = getProject();
compiler.setResourceSetProvider(new MavenProjectResourceSetProvider(project));
final Iterable<File> filtered = Iterables.filter(sourcePaths, (input) -> input.isDirectory());
if (Iterables.isEmpty(filtered)) {
final String dir = Iterables.toString(sourcePaths);
getLog().info(Locale.getString(AbstractSarlBatchCompilerMojo.class, "ERROR_0", dir)); //$NON-NLS-1$
return;
}
final String baseDir = project.getBasedir().getAbsolutePath();
compiler.setJavaPostCompilationEnable(getPostRunningOfJavaCompiler());
compiler.setClassOutputPath(makeAbsolute(new File(getProject().getBuild().getOutputDirectory())));
compiler.setJavaSourceVersion(getSourceVersion());
compiler.setBasePath(baseDir);
compiler.setTempDirectory(getTempDirectory());
compiler.setDeleteTempDirectory(false);
compiler.setClassPath(classPath);
final String bootClassPath = getBootClassPath();
compiler.setBootClassPath(bootClassPath);
final List<File> filteredSourcePaths = Lists.newArrayList(filtered);
compiler.setSourcePath(filteredSourcePaths);
compiler.setOutputPath(outputPath);
compiler.setFileEncoding(getEncoding());
compiler.setWriteTraceFiles(getGenerateTraceFiles());
compiler.setWriteStorageFiles(getGenerateStorageFiles());
compiler.setGenerateInlineAnnotation(getGenerateInlines());
final Logger logger = Logger.getLogger(getClass().getName(), new MavenLoggerFactory(getLog()));
compiler.setLogger(logger);
compiler.setIssueMessageFormatter((issue, uriToProblem) -> {
final String filename;
if (uriToProblem != null) {
filename = uriToProblem.toFileString();
} else {
filename = Locale.getString(AbstractSarlBatchCompilerMojo.class, "NO_FILE_NAME"); //$NON-NLS-1$
}
return Locale.getString(AbstractSarlBatchCompilerMojo.class, "ISSUE_MESSAGE", //$NON-NLS-1$
filename, issue.getLineNumber(),
issue.getColumn(), issue.getMessage());
});
if (!compiler.compile()) {
final StringBuilder dir = new StringBuilder();
for (final File file : filtered) {
if (dir.length() > 0) {
dir.append(File.pathSeparator);
}
dir.append(file.getAbsolutePath());
}
throw new MojoFailureException(Locale.getString(AbstractSarlBatchCompilerMojo.class, "ERROR_1")); //$NON-NLS-1$
}
}
private String getBootClassPath() throws MojoExecutionException {
final Toolchain toolchain = this.toolchainManager.getToolchainFromBuildContext("jdk", this.mavenHelper.getSession()); //$NON-NLS-1$
if (toolchain instanceof JavaToolChain && toolchain instanceof ToolchainPrivate) {
final JavaToolChain javaToolChain = (JavaToolChain) toolchain;
final ToolchainPrivate privateJavaToolChain = (ToolchainPrivate) toolchain;
getLog().info(Locale.getString(AbstractSarlBatchCompilerMojo.class, "USING_TOOLCHAIN", javaToolChain)); //$NON-NLS-1$
String[] includes = {"jre/lib/*", "jre/lib/ext/*", "jre/lib/endorsed/*"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String[] excludes = new String[0];
final Xpp3Dom config = (Xpp3Dom) privateJavaToolChain.getModel().getConfiguration();
if (config != null) {
final Xpp3Dom bootClassPath = config.getChild("bootClassPath"); //$NON-NLS-1$
if (bootClassPath != null) {
final Xpp3Dom includeParent = bootClassPath.getChild("includes"); //$NON-NLS-1$
if (includeParent != null) {
includes = getValues(includeParent.getChildren("include")); //$NON-NLS-1$
}
final Xpp3Dom excludeParent = bootClassPath.getChild("excludes"); //$NON-NLS-1$
if (excludeParent != null) {
excludes = getValues(excludeParent.getChildren("exclude")); //$NON-NLS-1$
}
}
}
try {
return scanBootclasspath(Objects.toString(this.reflect.invoke(javaToolChain, "getJavaHome")), includes, excludes); //$NON-NLS-1$
} catch (Exception e) {
throw new MojoExecutionException(e.getLocalizedMessage(), e);
}
}
return ""; //$NON-NLS-1$
}
private String scanBootclasspath(String javaHome, String[] includes, String[] excludes) {
getLog().debug(Locale.getString(AbstractSarlBatchCompilerMojo.class, "BOOTCLASSPATH", //$NON-NLS-1$
javaHome, Arrays.toString(includes), Arrays.toString(excludes)));
final DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(new File(javaHome));
scanner.setIncludes(includes);
scanner.setExcludes(excludes);
scanner.scan();
final StringBuilder bootClassPath = new StringBuilder();
final String[] includedFiles = scanner.getIncludedFiles();
for (int i = 0; i < includedFiles.length; i++) {
if (i > 0) {
bootClassPath.append(File.pathSeparator);
}
bootClassPath.append(new File(javaHome, includedFiles[i]).getAbsolutePath());
}
return bootClassPath.toString();
}
private static String[] getValues(Xpp3Dom[] children) {
final String[] values = new String[children.length];
for (int i = 0; i < values.length; i++) {
values[i] = children[i].getValue();
}
return values;
}
/** Replies temporary directory.
*
* @return the temporary directory.
*/
protected abstract File getTempDirectory();
/** Read the SARL Eclipse settings for the project if existing.
*
* @param sourceDirectory the source directory.
* @return the path from the settings.
*/
protected String readSarlEclipseSetting(String sourceDirectory) {
if (this.propertiesFileLocation != null) {
final File file = new File(this.propertiesFileLocation);
if (file.canRead()) {
final Properties sarlSettings = new Properties();
try (FileInputStream stream = new FileInputStream(file)) {
sarlSettings.load(stream);
// TODO read SARL setup to compute the properties file loc and property name
final String sarlOutputDirProp = sarlSettings.getProperty("outlet.DEFAULT_OUTPUT.directory", null); //$NON-NLS-1$
if (sarlOutputDirProp != null) {
final File srcDir = new File(sourceDirectory);
getLog().debug(Locale.getString(AbstractSarlBatchCompilerMojo.class, "SOURCE_DIR", //$NON-NLS-1$
srcDir.getPath(), srcDir.exists()));
if (srcDir.exists() && srcDir.getParent() != null) {
final String path = new File(srcDir.getParent(), sarlOutputDirProp).getPath();
getLog().debug(Locale.getString(AbstractSarlBatchCompilerMojo.class, "APPLY_PROPERTY", sarlOutputDirProp)); //$NON-NLS-1$
return path;
}
}
} catch (FileNotFoundException e) {
getLog().warn(e);
} catch (IOException e) {
getLog().warn(e);
}
} else {
getLog().debug(Locale.getString(AbstractSarlBatchCompilerMojo.class, "ERROR_2", this.propertiesFileLocation)); //$NON-NLS-1$
}
}
return null;
}
/** Replies the current classpath.
*
* @return the current classpath.
* @throws MojoExecutionException on failure.
*/
protected List<File> getClassPath() throws MojoExecutionException {
final Set<String> classPath = new LinkedHashSet<>();
final MavenProject project = getProject();
classPath.add(project.getBuild().getSourceDirectory());
try {
classPath.addAll(project.getCompileClasspathElements());
} catch (DependencyResolutionRequiredException e) {
throw new MojoExecutionException(e.getLocalizedMessage(), e);
}
for (final Artifact dep : project.getArtifacts()) {
classPath.add(dep.getFile().getAbsolutePath());
}
classPath.remove(project.getBuild().getOutputDirectory());
final List<File> files = new ArrayList<>();
for (final String filename : classPath) {
final File file = new File(filename);
if (file.exists()) {
files.add(file);
} else {
getLog().warn(Locale.getString(AbstractSarlBatchCompilerMojo.class, "ERROR_3", filename)); //$NON-NLS-1$
}
}
return files;
}
}