/*
* #%L
* Native ARchive plugin for Maven
* %%
* Copyright (C) 2002 - 2014 NAR Maven Plugin developers.
* %%
* 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.
* #L%
*/
package com.github.maven_nar;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
import com.github.maven_nar.cpptasks.CUtil;
import com.github.maven_nar.cpptasks.CompilerDef;
import com.github.maven_nar.cpptasks.CompilerEnum;
import com.github.maven_nar.cpptasks.OptimizationEnum;
import com.github.maven_nar.cpptasks.types.CompilerArgument;
import com.github.maven_nar.cpptasks.types.ConditionalFileSet;
import com.github.maven_nar.cpptasks.types.DefineArgument;
import com.github.maven_nar.cpptasks.types.DefineSet;
/**
* Abstract Compiler class
*
* @author Mark Donszelmann
*/
public abstract class Compiler {
public static final String MAIN = "main";
public static final String TEST = "test";
/**
* The name of the compiler. Some choices are: "msvc", "g++", "gcc", "CC",
* "cc", "icc", "icpc", ... Default is
* Architecture-OS-Linker specific: FIXME: table missing
*/
@Parameter
private String name;
/**
* The prefix for the compiler.
*/
@Parameter
private String prefix;
/**
* Path location of the compile tool
*/
@Parameter
private String toolPath;
/**
* Source directory for native files
*/
@Parameter(defaultValue = "${basedir}/src/main", required = true)
private File sourceDirectory;
/**
* Source directory for native test files
*/
@Parameter(defaultValue = "${basedir}/src/test", required = true)
private File testSourceDirectory;
/**
* To use full path for the filenames.
* false to have "relative" path
* true to have "absolute" path
* absolute: will give path from filesystem root "/"
* relative: will give relative path from "workdir" which is usually after "${basedir}/src/main"
*/
@Parameter(required = true)
private boolean gccFileAbsolutePath = false;
/**
* Include patterns for sources
*/
@Parameter(required = true)
private Set<String> includes = new HashSet<>();
/**
* Exclude patterns for sources
*/
@Parameter(required = true)
private Set<String> excludes = new HashSet<>();
/**
* Include patterns for test sources
*/
@Parameter(required = true)
private Set<String> testIncludes = new HashSet<>();
/**
* Exclude patterns for test sources
*/
@Parameter(required = true)
private Set<String> testExcludes = new HashSet<>();
@Parameter(defaultValue = "false", required = false)
private boolean ccache = false;
/**
* Compile with debug information.
*/
@Parameter(required = true)
private boolean debug = false;
/**
* Enables generation of exception handling code.
*/
@Parameter(defaultValue = "true", required = true)
private boolean exceptions = true;
/**
* Enables run-time type information.
*/
@Parameter(defaultValue = "true", required = true)
private boolean rtti = true;
/**
* Sets optimization. Possible choices are: "none", "size", "minimal",
* "speed", "full", "aggressive", "extreme",
* "unsafe".
*/
@Parameter(defaultValue = "none", required = true)
private String optimize = "none";
/**
* Enables or disables generation of multi-threaded code. Default value:
* false, except on Windows.
*/
@Parameter(required = true)
private boolean multiThreaded = false;
/**
* Defines
*/
@Parameter
private List<String> defines;
/**
* Defines for the compiler as a comma separated list of name[=value] pairs,
* where the value is optional. Will work
* in combination with <defines>.
*/
@Parameter
private String defineSet;
/**
* Clears default defines
*/
@Parameter(required = true)
private boolean clearDefaultDefines;
/**
* Undefines
*/
@Parameter
private List<String> undefines;
/**
* Undefines for the compiler as a comma separated list of name[=value] pairs
* where the value is optional. Will work
* in combination with <undefines>.
*/
@Parameter
private String undefineSet;
/**
* Clears default undefines
*/
@Parameter
private boolean clearDefaultUndefines;
/**
* Include Paths. Defaults to "${sourceDirectory}/include"
*/
@Parameter
private List<IncludePath> includePaths;
/**
* Test Include Paths. Defaults to "${testSourceDirectory}/include"
*/
@Parameter
private List<IncludePath> testIncludePaths;
/**
* System Include Paths, which are added at the end of all include paths
*/
@Parameter
private List<String> systemIncludePaths;
/**
* Additional options for the C++ compiler Defaults to Architecture-OS-Linker
* specific values. FIXME table missing
*/
@Parameter
private List<String> options;
/**
* Additional options for the compiler when running in the nar-testCompile
* phase.
*/
@Parameter
private List<String> testOptions;
/**
* Options for the compiler as a whitespace separated list. Will work in
* combination with <options>.
*/
@Parameter
private String optionSet;
/**
* Clears default options
*/
@Parameter(required = true)
private boolean clearDefaultOptions;
/**
* Comma separated list of filenames to compile in order
*/
@Parameter
private String compileOrder;
private AbstractCompileMojo mojo;
protected Compiler() {
}
/**
* Filter elements such as cr\lf that are problematic when used inside a `define`
*
* @param value define value to be cleaned
* @return
*/
private String cleanDefineValue(final String value) {
return value.replaceAll("\r", "").replaceAll("\n", ""); // ?maybe replace with chars \\n
}
public final void copyIncludeFiles(final MavenProject mavenProject, final File targetDirectory) throws IOException {
for (final IncludePath includePath : getIncludePaths("dummy")) {
if (includePath.exists()) {
NarUtil.copyDirectoryStructure(includePath.getFile(), targetDirectory, includePath.getIncludes(),
NarUtil.DEFAULT_EXCLUDES);
}
}
}
/**
* Generates a new {@link CompilerDef} and populates it give the parameters
* provided.
*
* @param type
* - main or test library - used to determine include and exclude
* paths.
* @param output
* - TODO Not sure..
* @return {@link CompilerDef} which contains the configuration for this
* compiler given the type and output.
* @throws MojoFailureException
* TODO
* @throws MojoExecutionException
* TODO
*/
public final CompilerDef getCompiler(final String type, final String output)
throws MojoFailureException, MojoExecutionException {
final String name = getName();
if (name == null) {
return null;
}
final CompilerDef compilerDef = new CompilerDef();
compilerDef.setProject(this.mojo.getAntProject());
final CompilerEnum compilerName = new CompilerEnum();
compilerName.setValue(name);
compilerDef.setName(compilerName);
// tool path
if (this.toolPath != null) {
compilerDef.setToolPath(this.toolPath);
} else if (Msvc.isMSVC(mojo)) {
mojo.getMsvc().setToolPath(compilerDef,getLanguage());
}
// debug, exceptions, rtti, multiThreaded
compilerDef.setCompilerPrefix(this.prefix);
compilerDef.setCcache(this.ccache);
compilerDef.setDebug(this.debug);
compilerDef.setExceptions(this.exceptions);
compilerDef.setRtti(this.rtti);
compilerDef.setMultithreaded(this.mojo.getOS().equals("Windows") || this.multiThreaded);
// optimize
final OptimizationEnum optimization = new OptimizationEnum();
optimization.setValue(this.optimize);
compilerDef.setOptimize(optimization);
// add options
if (this.options != null) {
for (final String string : this.options) {
final CompilerArgument arg = new CompilerArgument();
arg.setValue(string);
compilerDef.addConfiguredCompilerArg(arg);
}
}
if (this.optionSet != null) {
final String[] opts = this.optionSet.split("\\s");
for (final String opt : opts) {
final CompilerArgument arg = new CompilerArgument();
arg.setValue(opt);
compilerDef.addConfiguredCompilerArg(arg);
}
}
compilerDef.setClearDefaultOptions(this.clearDefaultOptions);
if (!this.clearDefaultOptions) {
final String optionsProperty = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
getPrefix() + "options");
if (optionsProperty != null) {
final String[] option = optionsProperty.split(" ");
for (final String element : option) {
final CompilerArgument arg = new CompilerArgument();
arg.setValue(element);
compilerDef.addConfiguredCompilerArg(arg);
}
}
}
// add defines
if (this.defines != null) {
final DefineSet ds = new DefineSet();
for (final String string : this.defines) {
final DefineArgument define = new DefineArgument();
final String[] pair = string.split("=", 2);
define.setName(pair[0]);
define.setValue(pair.length > 1 ? cleanDefineValue(pair[1]) : null);
ds.addDefine(define);
}
compilerDef.addConfiguredDefineset(ds);
}
if (this.defineSet != null) {
final String[] defList = this.defineSet.split(",");
final DefineSet defSet = new DefineSet();
for (final String element : defList) {
final String[] pair = element.trim().split("=", 2);
final DefineArgument def = new DefineArgument();
def.setName(pair[0]);
def.setValue(pair.length > 1 ? cleanDefineValue(pair[1]) : null);
defSet.addDefine(def);
}
compilerDef.addConfiguredDefineset(defSet);
}
if (!this.clearDefaultDefines) {
final DefineSet ds = new DefineSet();
final String defaultDefines = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
getPrefix() + "defines");
if (defaultDefines != null) {
ds.setDefine(new CUtil.StringArrayBuilder(defaultDefines));
}
compilerDef.addConfiguredDefineset(ds);
}
// add undefines
if (this.undefines != null) {
final DefineSet us = new DefineSet();
for (final String string : this.undefines) {
final DefineArgument undefine = new DefineArgument();
final String[] pair = string.split("=", 2);
undefine.setName(pair[0]);
undefine.setValue(pair.length > 1 ? pair[1] : null);
us.addUndefine(undefine);
}
compilerDef.addConfiguredDefineset(us);
}
if (this.undefineSet != null) {
final String[] undefList = this.undefineSet.split(",");
final DefineSet undefSet = new DefineSet();
for (final String element : undefList) {
final String[] pair = element.trim().split("=", 2);
final DefineArgument undef = new DefineArgument();
undef.setName(pair[0]);
undef.setValue(pair.length > 1 ? pair[1] : null);
undefSet.addUndefine(undef);
}
compilerDef.addConfiguredDefineset(undefSet);
}
if (!this.clearDefaultUndefines) {
final DefineSet us = new DefineSet();
final String defaultUndefines = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
getPrefix() + "undefines");
if (defaultUndefines != null) {
us.setUndefine(new CUtil.StringArrayBuilder(defaultUndefines));
}
compilerDef.addConfiguredDefineset(us);
}
// add include path
for (final IncludePath includePath : getIncludePaths(type)) {
// Darren Sargent, 30Jan2008 - fail build if invalid include path(s)
// specified.
if (!includePath.exists()) {
throw new MojoFailureException("NAR: Include path not found: " + includePath);
}
compilerDef.createIncludePath().setPath(includePath.getPath());
}
// add system include path (at the end)
if (this.systemIncludePaths != null) {
for (final String path : this.systemIncludePaths) {
compilerDef.createSysIncludePath().setPath(path);
}
}
// Add default fileset (if exists)
final List<File> srcDirs = getSourceDirectories(type);
final Set<String> includeSet = getIncludes(type);
final Set<String> excludeSet = getExcludes(type);
// now add all but the current test to the excludes
for (final Object o : this.mojo.getTests()) {
final Test test = (Test) o;
if (!test.getName().equals(output)) {
excludeSet.add("**/" + test.getName() + ".*");
}
}
for (final File srcDir : srcDirs) {
this.mojo.getLog().debug("Checking for existence of " + getLanguage() + " source directory: " + srcDir);
if (srcDir.exists()) {
if (this.compileOrder != null) {
compilerDef.setOrder(Arrays.asList(StringUtils.split(this.compileOrder, ", ")));
}
final ConditionalFileSet fileSet = new ConditionalFileSet();
fileSet.setProject(this.mojo.getAntProject());
fileSet.setIncludes(StringUtils.join(includeSet.iterator(), ","));
fileSet.setExcludes(StringUtils.join(excludeSet.iterator(), ","));
fileSet.setDir(srcDir);
compilerDef.addFileset(fileSet);
}
}
if (type.equals(TEST)) {
if (this.testSourceDirectory.exists()) {
compilerDef.setWorkDir(this.testSourceDirectory);
}
} else {
if (this.sourceDirectory.exists()) {
compilerDef.setWorkDir(this.sourceDirectory);
}
}
compilerDef.setGccFileAbsolutePath(this.gccFileAbsolutePath);
return compilerDef;
}
public final Set<String> getExcludes() throws MojoFailureException, MojoExecutionException {
return getExcludes("main");
}
protected final Set<String> getExcludes(final String type) throws MojoFailureException, MojoExecutionException {
final Set<String> result = new HashSet<>();
if (type.equals(TEST) && !this.testExcludes.isEmpty()) {
result.addAll(this.testExcludes);
} else if (!this.excludes.isEmpty()) {
result.addAll(this.excludes);
} else {
final String defaultExcludes = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
getPrefix() + "excludes");
if (defaultExcludes != null) {
final String[] exclude = defaultExcludes.split(" ");
for (final String element : exclude) {
result.add(element.trim());
}
}
}
return result;
}
protected final List<IncludePath> getIncludePaths(final String type) {
List<IncludePath> includeList = type.equals(TEST) ? this.testIncludePaths : this.includePaths;
if (includeList != null && includeList.size() != 0) {
return includeList;
}
includeList = new ArrayList<>();
for (final File file2 : getSourceDirectories(type)) {
// VR 20100318 only add include directories that exist - we now fail the
// build fast if an include directory does not exist
final File file = new File(file2, "include");
if (file.isDirectory()) {
final IncludePath includePath = new IncludePath();
includePath.setPath(file.getPath());
includeList.add(includePath);
}
}
return includeList;
}
public final Set<String> getIncludes() throws MojoFailureException, MojoExecutionException {
return getIncludes("main");
}
protected final Set<String> getIncludes(final String type) throws MojoFailureException, MojoExecutionException {
final Set<String> result = new HashSet<>();
if (!type.equals(TEST) && !this.includes.isEmpty()) {
result.addAll(this.includes);
} else if (type.equals(TEST) && !this.testIncludes.isEmpty()) {
result.addAll(this.testIncludes);
} else {
final String defaultIncludes = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
getPrefix() + "includes");
if (defaultIncludes != null) {
final String[] include = defaultIncludes.split(" ");
for (final String element : include) {
result.add(element.trim());
}
}
}
return result;
}
protected abstract String getLanguage();
public String getName() throws MojoFailureException, MojoExecutionException {
// adjust default values
if (this.name == null) {
this.name = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(getPrefix() + "compiler");
}
if (this.prefix == null) {
this.prefix = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(getPrefix() + "prefix");
}
return this.name;
}
protected final String getPrefix() throws MojoFailureException, MojoExecutionException {
return this.mojo.getAOL().getKey() + "." + getLanguage() + ".";
}
public final List<File> getSourceDirectories() {
return getSourceDirectories("dummy");
}
private List<File> getSourceDirectories(final String type) {
final List<File> sourceDirectories = new ArrayList<>();
final File baseDir = this.mojo.getMavenProject().getBasedir();
if (type.equals(TEST)) {
if (this.testSourceDirectory == null) {
this.testSourceDirectory = new File(baseDir, "/src/test");
}
if (this.testSourceDirectory.exists()) {
sourceDirectories.add(this.testSourceDirectory);
}
for (final Object element : this.mojo.getMavenProject().getTestCompileSourceRoots()) {
final File extraTestSourceDirectory = new File((String) element);
if (extraTestSourceDirectory.exists()) {
sourceDirectories.add(extraTestSourceDirectory);
}
}
} else {
if (this.sourceDirectory == null) {
this.sourceDirectory = new File(baseDir, "src/main");
}
if (this.sourceDirectory.exists()) {
sourceDirectories.add(this.sourceDirectory);
}
for (final Object element : this.mojo.getMavenProject().getCompileSourceRoots()) {
final File extraSourceDirectory = new File((String) element);
if (extraSourceDirectory.exists()) {
sourceDirectories.add(extraSourceDirectory);
}
}
}
if (this.mojo.getLog().isDebugEnabled()) {
for (final File file : sourceDirectories) {
this.mojo.getLog().debug("Added to sourceDirectory: " + file.getPath());
}
}
return sourceDirectories;
}
/**
* @return The standard Compiler configuration with 'testOptions' added to the
* argument list.
*/
public final CompilerDef getTestCompiler(final String type, final String output)
throws MojoFailureException, MojoExecutionException {
final CompilerDef compiler = getCompiler(type, output);
if (compiler != null && this.testOptions != null) {
for (final String string : this.testOptions) {
final CompilerArgument arg = new CompilerArgument();
arg.setValue(string);
compiler.addConfiguredCompilerArg(arg);
}
}
return compiler;
}
public final void setAbstractCompileMojo(final AbstractCompileMojo mojo) {
this.mojo = mojo;
}
@Override
public String toString() {
return NarUtil.prettyMavenString(this);
}
}