com.github.maven_nar.cpptasks.compiler.CommandLineCompiler.java Source code

Java tutorial

Introduction

Here is the source code for com.github.maven_nar.cpptasks.compiler.CommandLineCompiler.java

Source

/*
 * #%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;

    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");
            }
            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);
        }
        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;
    }
}