/*
* #%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.FileWriter;
import java.io.IOException;
import java.util.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Environment;
import com.github.maven_nar.cpptasks.CCTask;
import com.github.maven_nar.cpptasks.CUtil;
import com.github.maven_nar.cpptasks.LinkerDef;
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.LibrarySet;
/**
* An abstract Linker implementation that performs the link via an external
* command.
*
* @author Adam Murdoch
*/
public abstract class CommandLineLinker extends AbstractLinker {
private String command;
private String prefix;
private Environment env = null;
private String identifier;
private final String identifierArg;
private final boolean isLibtool;
private String[] librarySets;
private final CommandLineLinker libtoolLinker;
private final boolean newEnvironment = false;
private final String outputSuffix;
// FREEHEP
private final int maxPathLength = 250;
/** Creates a comand line linker invocation */
public CommandLineLinker(final String command, final String identifierArg, final String[] extensions,
final String[] ignoredExtensions, final String outputSuffix, final boolean isLibtool,
final CommandLineLinker libtoolLinker) {
super(extensions, ignoredExtensions);
this.command = command;
this.identifierArg = identifierArg;
this.outputSuffix = outputSuffix;
this.isLibtool = isLibtool;
this.libtoolLinker = libtoolLinker;
}
protected void addBase(final CCTask task, final long base, final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addEntry(final CCTask task, final String entry, final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addFixed(final CCTask task, final Boolean fixed, final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addImpliedArgs(final CCTask task, final boolean debug, final LinkType linkType,
final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addIncremental(final CCTask task, final boolean incremental, final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addLibraryDirectory(final File libraryDirectory, final Vector<String> preargs) {
try {
if (libraryDirectory != null && libraryDirectory.exists()) {
final File currentDir = new File(".").getParentFile();
String path = libraryDirectory.getCanonicalPath();
if (currentDir != null) {
final String currentPath = currentDir.getCanonicalPath();
path = CUtil.getRelativePath(currentPath, libraryDirectory);
}
addLibraryPath(preargs, path);
}
} catch (final IOException e) {
throw new RuntimeException("Unable to add library path: " + libraryDirectory);
}
}
protected void addLibraryPath(final Vector<String> preargs, final String path) {
}
//
// Windows processors handle these through file list
//
protected String[] addLibrarySets(final CCTask task, final LibrarySet[] libsets, final Vector<String> preargs,
final Vector<String> midargs, final Vector<String> endargs) {
return null;
}
protected void addMap(final CCTask task, final boolean map, final Vector<String> args) {
// NB: Do nothing by default.
}
protected void addStack(final CCTask task, final int stack, final Vector<String> args) {
// NB: Do nothing by default.
}
@Override
protected LinkerConfiguration createConfiguration(final CCTask task, final LinkType linkType,
final ProcessorDef[] baseDefs, final LinkerDef specificDef, final TargetDef targetPlatform,
final VersionInfo versionInfo) {
final Vector<String> preargs = new Vector<>();
final Vector<String> midargs = new Vector<>();
final Vector<String> endargs = new Vector<>();
final Vector<String>[] args = new Vector[] {
preargs, midargs, endargs
};
this.prefix = specificDef.getLinkerPrefix();
final LinkerDef[] defaultProviders = new LinkerDef[baseDefs.length + 1];
defaultProviders[0] = specificDef;
for (int i = 0; i < baseDefs.length; i++) {
defaultProviders[i + 1] = (LinkerDef) baseDefs[i];
}
//
// 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--) {
final LinkerDef linkerDef = defaultProviders[i];
commandArgs = linkerDef.getActiveProcessorArgs();
for (final CommandLineArgument commandArg : commandArgs) {
args[commandArg.getLocation()].addElement(commandArg.getValue());
}
}
final Set<File> libraryDirectories = new LinkedHashSet<>();
for (int i = defaultProviders.length - 1; i >= 0; i--) {
final LinkerDef linkerDef = defaultProviders[i];
for (final File libraryDirectory : linkerDef.getLibraryDirectories()) {
if (libraryDirectories.add(libraryDirectory)) {
addLibraryDirectory(libraryDirectory, preargs);
}
}
}
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()]);
final boolean debug = specificDef.getDebug(baseDefs, 0);
final String startupObject = getStartupObject(linkType);
addImpliedArgs(task, debug, linkType, preargs);
addIncremental(task, specificDef.getIncremental(defaultProviders, 1), preargs);
addFixed(task, specificDef.getFixed(defaultProviders, 1), preargs);
addMap(task, specificDef.getMap(defaultProviders, 1), preargs);
addBase(task, specificDef.getBase(defaultProviders, 1), preargs);
addStack(task, specificDef.getStack(defaultProviders, 1), preargs);
addEntry(task, specificDef.getEntry(defaultProviders, 1), preargs);
String[] libnames = null;
final LibrarySet[] libsets = specificDef.getActiveLibrarySets(defaultProviders, 1);
// FREEHEP call at all times
// if (libsets.length > 0) {
libnames = addLibrarySets(task, libsets, preargs, midargs, endargs);
// }
final StringBuffer buf = new StringBuffer(getIdentifier());
for (int i = 0; i < 3; i++) {
final Enumeration<String> argenum = args[i].elements();
while (argenum.hasMoreElements()) {
buf.append(' ');
buf.append(argenum.nextElement());
}
}
final String configId = buf.toString();
final String[][] options = new String[][] {
new String[args[0].size() + args[1].size()], new String[args[2].size()]
};
args[0].copyInto(options[0]);
final int offset = args[0].size();
for (int i = 0; i < args[1].size(); i++) {
options[0][i + offset] = args[1].elementAt(i);
}
args[2].copyInto(options[1]);
// if this linker doesn't have an env, and there is a more generically
// definition for environment, use it.
if (null != specificDef.getEnv() && null == this.env) {
this.env = specificDef.getEnv();
}
for (final ProcessorDef processorDef : baseDefs) {
final Environment environment = processorDef.getEnv();
if (null != environment && null == this.env) {
this.env = environment;
}
}
final boolean rebuild = specificDef.getRebuild(baseDefs, 0);
final boolean map = specificDef.getMap(defaultProviders, 1);
final String toolPath = specificDef.getToolPath();
// task.log("libnames:"+libnames.length, Project.MSG_VERBOSE);
return new CommandLineLinkerConfiguration(this, configId, options, paramArray, rebuild, map, debug, libnames,
startupObject, toolPath);
}
/**
* Allows drived linker to decorate linker option.
* Override by GccLinker to prepend a "-Wl," to
* pass option to through gcc to linker.
*
* @param buf
* buffer that may be used and abused in the decoration process,
* must not be null.
* @param arg
* linker argument
*/
protected String decorateLinkerOption(final StringBuffer buf, final String arg) {
return arg;
}
protected final String getCommand() {
if (this.prefix != null && (!this.prefix.isEmpty())) {
return this.prefix + this.command;
} else {
return this.command;
}
}
protected abstract String getCommandFileSwitch(String commandFile);
public String getCommandWithPath(final CommandLineLinkerConfiguration 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();
}
}
@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;
}
public final CommandLineLinker getLibtoolLinker() {
if (this.libtoolLinker != null) {
return this.libtoolLinker;
}
return this;
}
protected abstract int getMaximumCommandLength();
@Override
public String[] getOutputFileNames(final String baseName, final VersionInfo versionInfo) {
return new String[] {
baseName + this.outputSuffix
};
}
protected String[] getOutputFileSwitch(final CCTask task, final String outputFile) {
// FREEHEP BEGIN
if (isWindows() && outputFile.length() > this.maxPathLength) {
throw new BuildException("Absolute path too long, " + outputFile.length() + " > " + this.maxPathLength + ": '"
+ outputFile);
}
// FREEHEP END
return getOutputFileSwitch(outputFile);
}
protected abstract String[] getOutputFileSwitch(String outputFile);
protected String getStartupObject(final LinkType linkType) {
return null;
}
/**
* Performs a link using a command line linker
*
*/
public void link(final CCTask task, final File outputFile, final String[] sourceFiles,
final CommandLineLinkerConfiguration config) throws BuildException {
final File parentDir = new File(outputFile.getParent());
String parentPath;
try {
parentPath = parentDir.getCanonicalPath();
} catch (final IOException ex) {
parentPath = parentDir.getAbsolutePath();
}
String[] execArgs = prepareArguments(task, parentPath, outputFile.getName(), sourceFiles, config);
int commandLength = 0;
for (final String execArg : execArgs) {
commandLength += execArg.length() + 1;
}
//
// if command length exceeds maximum
// then create a temporary
// file containing everything but the command name
if (commandLength >= this.getMaximumCommandLength()) {
try {
execArgs = prepareResponseFile(outputFile, execArgs);
} catch (final IOException ex) {
throw new BuildException(ex);
}
}
final int retval = runCommand(task, parentDir, execArgs);
//
// if the process returned a failure code then
// throw an BuildException
//
if (retval != 0) {
//
// construct the exception
//
throw new BuildException(getCommandWithPath(config) + " failed with return code " + retval, task.getLocation());
}
}
/**
* Prepares argument list for exec command. Will return null
* if command line would exceed allowable command line buffer.
*
* @param task
* compilation task.
* @param outputFile
* linker output file
* @param sourceFiles
* linker input files (.obj, .o, .res)
* @param config
* linker configuration
* @return arguments for runTask
*/
protected String[] prepareArguments(final CCTask task, final String outputDir, final String outputFile,
final String[] sourceFiles, final CommandLineLinkerConfiguration config) {
final String[] preargs = config.getPreArguments();
final String[] endargs = config.getEndArguments();
final String outputSwitch[] = getOutputFileSwitch(task, outputFile);
int allArgsCount = preargs.length + 1 + outputSwitch.length + sourceFiles.length + endargs.length;
if (this.isLibtool) {
allArgsCount++;
}
final String[] allArgs = new String[allArgsCount];
int index = 0;
if (this.isLibtool) {
allArgs[index++] = "libtool";
}
allArgs[index++] = getCommandWithPath(config);
final StringBuffer buf = new StringBuffer();
for (final String prearg : preargs) {
allArgs[index++] = task.isDecorateLinkerOptions() ? decorateLinkerOption(buf, prearg) : prearg;
}
for (final String element : outputSwitch) {
allArgs[index++] = element;
}
for (final String sourceFile : sourceFiles) {
allArgs[index++] = prepareFilename(buf, outputDir, sourceFile);
}
for (final String endarg : endargs) {
allArgs[index++] = task.isDecorateLinkerOptions() ? decorateLinkerOption(buf, endarg) : endarg;
}
return allArgs;
}
/**
* Processes filename into argument form
*
*/
protected String prepareFilename(final StringBuffer buf, final String outputDir, final String sourceFile) {
// FREEHEP BEGIN exit if absolute path is too long. Max length on relative
// paths in windows is even shorter.
if (isWindows() && sourceFile.length() > this.maxPathLength) {
throw new BuildException("Absolute path too long, " + sourceFile.length() + " > " + this.maxPathLength + ": '"
+ sourceFile);
}
// FREEHEP END
return quoteFilename(buf, sourceFile);
}
/**
* Prepares argument list to execute the linker using a
* response file.
*
* @param outputFile
* linker output file
* @param args
* output of prepareArguments
* @return arguments for runTask
*/
protected String[] prepareResponseFile(final File outputFile, final String[] args) throws IOException {
final String baseName = outputFile.getName();
final File commandFile = new File(outputFile.getParent(), baseName + ".rsp");
final FileWriter writer = new FileWriter(commandFile);
int execArgCount = 1;
if (this.isLibtool) {
execArgCount++;
}
final String[] execArgs = new String[execArgCount + 1];
System.arraycopy(args, 0, execArgs, 0, execArgCount);
execArgs[execArgCount] = getCommandFileSwitch(commandFile.toString());
for (int i = execArgCount; i < args.length; i++) {
//
// if embedded space and not quoted then
// quote argument
if (args[i].contains(" ") && args[i].charAt(0) != '\"') {
writer.write('\"');
writer.write(args[i]);
writer.write("\"\n");
} else {
writer.write(args[i]);
writer.write('\n');
}
}
writer.close();
return execArgs;
}
protected String quoteFilename(final StringBuffer buf, final String filename) {
if (filename.indexOf(' ') >= 0) {
buf.setLength(0);
buf.append('\"');
buf.append(filename);
buf.append('\"');
return buf.toString();
}
return filename;
}
/**
* 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;
}
}