/*
* #%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.cpptasks.compiler;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;
import java.util.ArrayList;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Environment;
import org.apache.commons.io.FilenameUtils;
import com.github.maven_nar.cpptasks.CCTask;
import com.github.maven_nar.cpptasks.CUtil;
import com.github.maven_nar.cpptasks.CompilerDef;
import com.github.maven_nar.cpptasks.OptimizationEnum;
import com.github.maven_nar.cpptasks.ProcessorDef;
import com.github.maven_nar.cpptasks.ProcessorParam;
import com.github.maven_nar.cpptasks.TargetDef;
import com.github.maven_nar.cpptasks.VersionInfo;
import com.github.maven_nar.cpptasks.types.CommandLineArgument;
import com.github.maven_nar.cpptasks.types.UndefineArgument;
import com.google.common.collect.ObjectArrays;
import org.apache.tools.ant.util.FileUtils;
/**
* An abstract Compiler implementation which uses an external program to
* perform the compile.
*
* @author Adam Murdoch
*/
public abstract class CommandLineCompiler extends AbstractCompiler {
/** Command used when invoking ccache */
private static final String CCACHE_CMD = "ccache";
private String command;
private String prefix;
private final Environment env;
private String identifier;
private final String identifierArg;
private final boolean libtool;
private final CommandLineCompiler libtoolCompiler;
private final boolean newEnvironment;
private String fortifyID="";
protected CommandLineCompiler(final String command, final String identifierArg, final String[] sourceExtensions,
final String[] headerExtensions,
final String outputSuffix, final boolean libtool, final CommandLineCompiler libtoolCompiler,
final boolean newEnvironment, final Environment env) {
super(sourceExtensions, headerExtensions, outputSuffix);
this.command = command;
if (libtool && libtoolCompiler != null) {
throw new java.lang.IllegalArgumentException("libtoolCompiler should be null when libtool is true");
}
this.libtool = libtool;
this.libtoolCompiler = libtoolCompiler;
this.identifierArg = identifierArg;
this.newEnvironment = newEnvironment;
this.env = env;
}
abstract protected void addImpliedArgs(Vector<String> args, boolean debug, boolean multithreaded, boolean exceptions,
LinkType linkType, Boolean rtti, OptimizationEnum optimization);
/**
* Adds command-line arguments for include directories.
*
* If relativeArgs is not null will add corresponding relative paths
* include switches to that vector (for use in building a configuration
* identifier that is consistent between machines).
*
* @param baseDirPath
* Base directory path.
* @param includeDirs
* Array of include directory paths
* @param args
* Vector of command line arguments used to execute the task
* @param relativeArgs
* Vector of command line arguments used to build the
* configuration identifier
*/
protected void addIncludes(final String baseDirPath, final File[] includeDirs, final Vector<String> args,
final Vector<String> relativeArgs, final StringBuffer includePathId, final boolean isSystem) {
for (final File includeDir : includeDirs) {
args.addElement(getIncludeDirSwitch(includeDir.getAbsolutePath(), isSystem));
if (relativeArgs != null) {
final String relative = CUtil.getRelativePath(baseDirPath, includeDir);
relativeArgs.addElement(getIncludeDirSwitch(relative, isSystem));
if (includePathId != null) {
if (includePathId.length() == 0) {
includePathId.append("/I");
} else {
includePathId.append(" /I");
}
includePathId.append(relative);
}
}
}
}
abstract protected void addWarningSwitch(Vector<String> args, int warnings);
protected void buildDefineArguments(final CompilerDef[] defs, final Vector<String> args) {
//
// assume that we aren't inheriting defines from containing <cc>
//
UndefineArgument[] merged = defs[0].getActiveDefines();
for (int i = 1; i < defs.length; i++) {
//
// if we are inheriting, merge the specific defines with the
// containing defines
merged = UndefineArgument.merge(defs[i].getActiveDefines(), merged);
}
final StringBuffer buf = new StringBuffer(30);
for (final UndefineArgument current : merged) {
buf.setLength(0);
if (current.isDefine()) {
getDefineSwitch(buf, current.getName(), current.getValue());
} else {
getUndefineSwitch(buf, current.getName());
}
args.addElement(buf.toString());
}
}
@Override
public String[] getOutputFileNames(final String inputFile, final VersionInfo versionInfo) {
//
// if a recognized input file
//
if (bid(inputFile) > 1) {
final String baseName = getBaseOutputName(inputFile);
final File standardisedFile = new File(inputFile);
try {
return new String[] {
baseName + FilenameUtils.EXTENSION_SEPARATOR + Integer.toHexString(standardisedFile.getCanonicalPath().hashCode()) + getOutputSuffix()
};
} catch (IOException e) {
throw new BuildException("Source file not found", e);
}
}
return new String[0];
}
/**
* Compiles a source file.
*
*/
public void compile(final CCTask task, final File outputDir, final String[] sourceFiles, String[] args,
final String[] endArgs, final boolean relentless, final CommandLineCompilerConfiguration config,
final ProgressMonitor monitor) throws BuildException {
BuildException exc = null;
//
// determine length of executable name and args
//
String command = getCommandWithPath(config);
if (config.isUseCcache()) {
// Replace the command with "ccache" and push the old compiler
// command into the args.
final String compilerCommand = command;
command = CCACHE_CMD;
args = ObjectArrays.concat(compilerCommand, args);
}
int baseLength = command.length() + args.length + endArgs.length;
if (this.libtool) {
baseLength += 8;
}
for (final String arg : args) {
baseLength += arg.length();
}
for (final String endArg : endArgs) {
baseLength += endArg.length();
}
if (baseLength > getMaximumCommandLength()) {
throw new BuildException("Command line is over maximum length without specifying source file");
}
//
// typically either 1 or Integer.MAX_VALUE
//
final int maxInputFilesPerCommand = getMaximumInputFilesPerCommand();
final int argumentCountPerInputFile = getArgumentCountPerInputFile();
for (int sourceIndex = 0; sourceIndex < sourceFiles.length;) {
int cmdLength = baseLength;
int firstFileNextExec;
for (firstFileNextExec = sourceIndex; firstFileNextExec < sourceFiles.length
&& firstFileNextExec - sourceIndex < maxInputFilesPerCommand; firstFileNextExec++) {
cmdLength += getTotalArgumentLengthForInputFile(outputDir, sourceFiles[firstFileNextExec]);
if (cmdLength >= getMaximumCommandLength()) {
break;
}
}
if (firstFileNextExec == sourceIndex) {
throw new BuildException("Extremely long file name, can't fit on command line");
}
ArrayList<String> commandlinePrefix = new ArrayList<>();
if (this.libtool) {
commandlinePrefix.add("libtool");
}
if((this.fortifyID !=null) && (!this.fortifyID.equals("")))
{// If FortifyID attribute was set, run the Fortify framework
commandlinePrefix.add("sourceanalyzer");
commandlinePrefix.add("-b");
commandlinePrefix.add(this.fortifyID);
}
commandlinePrefix.add(command);
Collections.addAll(commandlinePrefix, args);
int retval = 0;
for (int j = sourceIndex; j < firstFileNextExec; j++) {
ArrayList<String> commandlineSuffix = new ArrayList<>();
for (int k = 0; k < argumentCountPerInputFile; k++) {
commandlineSuffix.add(getInputFileArgument(outputDir, sourceFiles[j], k));
}
Collections.addAll(commandlineSuffix, endArgs);
ArrayList<String> commandline = new ArrayList<>(commandlinePrefix);
commandline.addAll(commandlineSuffix);
final int ret = runCommand(task, workDir,
commandline.toArray(new String[commandline.size()]));
if (ret != 0) { retval = ret; }
}
if (monitor != null) {
final String[] fileNames = new String[firstFileNextExec - sourceIndex];
System.arraycopy(sourceFiles, sourceIndex + 0, fileNames, 0, fileNames.length);
monitor.progress(fileNames);
}
//
// if the process returned a failure code and
// we aren't holding an exception from an earlier
// interation
if (retval != 0 && exc == null) {
//
// construct the exception
//
exc = new BuildException(getCommandWithPath(config) + " failed with return code " + retval, task.getLocation());
//
// and throw it now unless we are relentless
//
if (!relentless) {
throw exc;
}
}
sourceIndex = firstFileNextExec;
}
//
// if the compiler returned a failure value earlier
// then throw an exception
if (exc != null) {
throw exc;
}
}
@Override
protected CompilerConfiguration createConfiguration(final CCTask task, final LinkType linkType,
final ProcessorDef[] baseDefs, final CompilerDef specificDef, final TargetDef targetPlatform,
final VersionInfo versionInfo) {
this.prefix = specificDef.getCompilerPrefix();
this.objDir = task.getObjdir();
final Vector<String> args = new Vector<>();
final CompilerDef[] defaultProviders = new CompilerDef[baseDefs.length + 1];
for (int i = 0; i < baseDefs.length; i++) {
defaultProviders[i + 1] = (CompilerDef) baseDefs[i];
}
defaultProviders[0] = specificDef;
final Vector<CommandLineArgument> cmdArgs = new Vector<>();
//
// add command line arguments inherited from <cc> element
// any "extends" and finally the specific CompilerDef
CommandLineArgument[] commandArgs;
for (int i = defaultProviders.length - 1; i >= 0; i--) {
commandArgs = defaultProviders[i].getActiveProcessorArgs();
for (final CommandLineArgument commandArg : commandArgs) {
if (commandArg.getLocation() == 0) {
String arg = commandArg.getValue();
if (isWindows() && arg.matches(".*[ \"].*")) {
// Work around inconsistent quoting by Ant
arg = "\"" + arg.replaceAll("[\\\\\"]", "\\\\$0") + "\"";
}
args.addElement(arg);
} else {
cmdArgs.addElement(commandArg);
}
}
}
final Vector<ProcessorParam> params = new Vector<>();
//
// add command line arguments inherited from <cc> element
// any "extends" and finally the specific CompilerDef
ProcessorParam[] paramArray;
for (int i = defaultProviders.length - 1; i >= 0; i--) {
paramArray = defaultProviders[i].getActiveProcessorParams();
Collections.addAll(params, paramArray);
}
paramArray = params.toArray(new ProcessorParam[params.size()]);
if (specificDef.isClearDefaultOptions() == false) {
final boolean multithreaded = specificDef.getMultithreaded(defaultProviders, 1);
final boolean debug = specificDef.getDebug(baseDefs, 0);
final boolean exceptions = specificDef.getExceptions(defaultProviders, 1);
final Boolean rtti = specificDef.getRtti(defaultProviders, 1);
final OptimizationEnum optimization = specificDef.getOptimization(defaultProviders, 1);
this.addImpliedArgs(args, debug, multithreaded, exceptions, linkType, rtti, optimization);
}
//
// add all appropriate defines and undefines
//
buildDefineArguments(defaultProviders, args);
final int warnings = specificDef.getWarnings(defaultProviders, 0);
addWarningSwitch(args, warnings);
Enumeration<CommandLineArgument> argEnum = cmdArgs.elements();
int endCount = 0;
while (argEnum.hasMoreElements()) {
final CommandLineArgument arg = argEnum.nextElement();
switch (arg.getLocation()) {
case 1:
args.addElement(arg.getValue());
break;
case 2:
endCount++;
break;
}
}
final String[] endArgs = new String[endCount];
argEnum = cmdArgs.elements();
int index = 0;
while (argEnum.hasMoreElements()) {
final CommandLineArgument arg = argEnum.nextElement();
if (arg.getLocation() == 2) {
endArgs[index++] = arg.getValue();
}
}
//
// Want to have distinct set of arguments with relative
// path names for includes that are used to build
// the configuration identifier
//
final Vector<String> relativeArgs = (Vector) args.clone();
//
// add all active include and sysincludes
//
final StringBuffer includePathIdentifier = new StringBuffer();
final File baseDir = specificDef.getProject().getBaseDir();
String baseDirPath;
try {
baseDirPath = baseDir.getCanonicalPath();
} catch (final IOException ex) {
baseDirPath = baseDir.toString();
}
final Vector<String> includePath = new Vector<>();
final Vector<String> sysIncludePath = new Vector<>();
for (int i = defaultProviders.length - 1; i >= 0; i--) {
String[] incPath = defaultProviders[i].getActiveIncludePaths();
for (final String element : incPath) {
includePath.addElement(element);
}
incPath = defaultProviders[i].getActiveSysIncludePaths();
for (final String element : incPath) {
sysIncludePath.addElement(element);
}
}
final File[] incPath = new File[includePath.size()];
for (int i = 0; i < includePath.size(); i++) {
incPath[i] = new File(includePath.elementAt(i));
}
final File[] sysIncPath = new File[sysIncludePath.size()];
for (int i = 0; i < sysIncludePath.size(); i++) {
sysIncPath[i] = new File(sysIncludePath.elementAt(i));
}
addIncludes(baseDirPath, incPath, args, relativeArgs, includePathIdentifier, false);
addIncludes(baseDirPath, sysIncPath, args, null, null, true);
final StringBuffer buf = new StringBuffer(getIdentifier());
for (int i = 0; i < relativeArgs.size(); i++) {
buf.append(' ');
buf.append(relativeArgs.elementAt(i));
}
for (final String endArg : endArgs) {
buf.append(' ');
buf.append(endArg);
}
final String configId = buf.toString();
final String[] argArray = new String[args.size()];
args.copyInto(argArray);
final boolean rebuild = specificDef.getRebuild(baseDefs, 0);
final File[] envIncludePath = getEnvironmentIncludePath();
final String path = specificDef.getToolPath();
CommandLineCompiler compiler = this;
Environment environment = specificDef.getEnv();
if (environment == null) {
for (final ProcessorDef baseDef : baseDefs) {
environment = baseDef.getEnv();
if (environment != null) {
compiler = (CommandLineCompiler) compiler.changeEnvironment(baseDef.isNewEnvironment(), environment);
}
}
} else {
compiler = (CommandLineCompiler) compiler.changeEnvironment(specificDef.isNewEnvironment(), environment);
}
// Pass the fortifyID for compiler
compiler.fortifyID = specificDef.getFortifyID();
return new CommandLineCompilerConfiguration(compiler, configId, incPath, sysIncPath, envIncludePath,
includePathIdentifier.toString(), argArray, paramArray, rebuild, endArgs, path, specificDef.getCcache());
}
protected int getArgumentCountPerInputFile() {
return 1;
}
protected final String getCommand() {
if (this.prefix != null && (!this.prefix.isEmpty())) {
return this.prefix + this.command;
} else {
return this.command;
}
}
public String getCommandWithPath(final CommandLineCompilerConfiguration config) {
if (config.getCommandPath() != null) {
final File command = new File(config.getCommandPath(), this.getCommand());
try {
return command.getCanonicalPath();
} catch (final IOException e) {
e.printStackTrace();
return command.getAbsolutePath();
}
} else {
return this.getCommand();
}
}
abstract protected void getDefineSwitch(StringBuffer buffer, String define, String value);
protected abstract File[] getEnvironmentIncludePath();
@Override
public String getIdentifier() {
if (this.identifier == null) {
if (this.identifierArg == null) {
this.identifier = getIdentifier(new String[] {
this.getCommand()
}, this.getCommand());
} else {
this.identifier = getIdentifier(new String[] {
this.getCommand(), this.identifierArg
}, this.getCommand());
}
}
return this.identifier;
}
abstract protected String getIncludeDirSwitch(String source);
/**
* Added by Darren Sargent 22Oct2008 Returns the include dir switch value.
* Default implementation doesn't treat system includes specially, for
* compilers which don't care.
*
* @param source
* the given source value.
* @param isSystem
* "true" if this is a system include path
*
* @return the include dir switch value.
*/
protected String getIncludeDirSwitch(final String source, final boolean isSystem) {
return getIncludeDirSwitch(source);
}
protected String getInputFileArgument(final File outputDir, final String filename, final int index) {
//
// if there is an embedded space,
// must enclose in quotes
String relative="";
String inputFile;
try {
relative = FileUtils.getRelativePath(workDir, new File(filename));
} catch (Exception ex) {
}
if (relative.isEmpty()) {
inputFile = filename;
} else {
inputFile = relative;
}
if (inputFile.indexOf(' ') >= 0) {
final String buf = "\"" + inputFile +
"\"";
return buf;
}
return inputFile;
}
protected final boolean getLibtool() {
return this.libtool;
}
/**
* Obtains the same compiler, but with libtool set
*
* Default behavior is to ignore libtool
*/
public final CommandLineCompiler getLibtoolCompiler() {
if (this.libtoolCompiler != null) {
return this.libtoolCompiler;
}
return this;
}
abstract public int getMaximumCommandLength();
protected int getMaximumInputFilesPerCommand() {
return Integer.MAX_VALUE;
}
/**
* Get total command line length due to the input file.
*
* @param outputDir
* File output directory
* @param inputFile
* String input file
* @return int characters added to command line for the input file.
*/
protected int getTotalArgumentLengthForInputFile(final File outputDir, final String inputFile) {
final int argumentCountPerInputFile = getArgumentCountPerInputFile();
int len=0;
for (int k = 0; k < argumentCountPerInputFile; k++) {
len+=getInputFileArgument(outputDir, inputFile, k).length();
}
return len + argumentCountPerInputFile; // argumentCountPerInputFile added for spaces
}
abstract protected void getUndefineSwitch(StringBuffer buffer, String define);
/**
* This method is exposed so test classes can overload and test the
* arguments without actually spawning the compiler
*/
protected int runCommand(final CCTask task, final File workingDir, final String[] cmdline) throws BuildException {
return CUtil.runCommand(task, workingDir, cmdline, this.newEnvironment, this.env);
}
protected final void setCommand(final String command) {
this.command = command;
}
}