View Javadoc

1   /*
2    * #%L
3    * Native ARchive plugin for Maven
4    * %%
5    * Copyright (C) 2002 - 2014 NAR Maven Plugin developers.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   * http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.github.maven_nar.cpptasks.compiler;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.Vector;
27  import java.util.ArrayList;
28  
29  import org.apache.tools.ant.BuildException;
30  import org.apache.tools.ant.types.Environment;
31  import org.apache.commons.io.FilenameUtils;
32  
33  import com.github.maven_nar.cpptasks.CCTask;
34  import com.github.maven_nar.cpptasks.CUtil;
35  import com.github.maven_nar.cpptasks.CompilerDef;
36  import com.github.maven_nar.cpptasks.OptimizationEnum;
37  import com.github.maven_nar.cpptasks.ProcessorDef;
38  import com.github.maven_nar.cpptasks.ProcessorParam;
39  import com.github.maven_nar.cpptasks.TargetDef;
40  import com.github.maven_nar.cpptasks.VersionInfo;
41  import com.github.maven_nar.cpptasks.types.CommandLineArgument;
42  import com.github.maven_nar.cpptasks.types.UndefineArgument;
43  import com.google.common.collect.ObjectArrays;
44  import org.apache.tools.ant.util.FileUtils;
45  
46  /**
47   * An abstract Compiler implementation which uses an external program to
48   * perform the compile.
49   *
50   * @author Adam Murdoch
51   */
52  public abstract class CommandLineCompiler extends AbstractCompiler {
53    /** Command used when invoking ccache */
54    private static final String CCACHE_CMD = "ccache";
55    private String command;
56    private String prefix;
57    private final Environment env;
58    private String identifier;
59    private final String identifierArg;
60    private final boolean libtool;
61    private final CommandLineCompiler libtoolCompiler;
62    private final boolean newEnvironment;
63  
64    protected CommandLineCompiler(final String command, final String identifierArg, final String[] sourceExtensions,
65        final String[] headerExtensions,
66        final String outputSuffix, final boolean libtool, final CommandLineCompiler libtoolCompiler,
67        final boolean newEnvironment, final Environment env) {
68      super(sourceExtensions, headerExtensions, outputSuffix);
69      this.command = command;
70      if (libtool && libtoolCompiler != null) {
71        throw new java.lang.IllegalArgumentException("libtoolCompiler should be null when libtool is true");
72      }
73      this.libtool = libtool;
74      this.libtoolCompiler = libtoolCompiler;
75      this.identifierArg = identifierArg;
76      this.newEnvironment = newEnvironment;
77      this.env = env;
78    }
79  
80    abstract protected void addImpliedArgs(Vector<String> args, boolean debug, boolean multithreaded, boolean exceptions,
81        LinkType linkType, Boolean rtti, OptimizationEnum optimization);
82  
83    /**
84     * Adds command-line arguments for include directories.
85     * 
86     * If relativeArgs is not null will add corresponding relative paths
87     * include switches to that vector (for use in building a configuration
88     * identifier that is consistent between machines).
89     * 
90     * @param baseDirPath
91     *          Base directory path.
92     * @param includeDirs
93     *          Array of include directory paths
94     * @param args
95     *          Vector of command line arguments used to execute the task
96     * @param relativeArgs
97     *          Vector of command line arguments used to build the
98     *          configuration identifier
99     */
100   protected void addIncludes(final String baseDirPath, final File[] includeDirs, final Vector<String> args,
101       final Vector<String> relativeArgs, final StringBuffer includePathId, final boolean isSystem) {
102     for (final File includeDir : includeDirs) {
103       args.addElement(getIncludeDirSwitch(includeDir.getAbsolutePath(), isSystem));
104       if (relativeArgs != null) {
105         final String relative = CUtil.getRelativePath(baseDirPath, includeDir);
106         relativeArgs.addElement(getIncludeDirSwitch(relative, isSystem));
107         if (includePathId != null) {
108           if (includePathId.length() == 0) {
109             includePathId.append("/I");
110           } else {
111             includePathId.append(" /I");
112           }
113           includePathId.append(relative);
114         }
115       }
116     }
117   }
118 
119   abstract protected void addWarningSwitch(Vector<String> args, int warnings);
120 
121   protected void buildDefineArguments(final CompilerDef[] defs, final Vector<String> args) {
122     //
123     // assume that we aren't inheriting defines from containing <cc>
124     //
125     UndefineArgument[] merged = defs[0].getActiveDefines();
126     for (int i = 1; i < defs.length; i++) {
127       //
128       // if we are inheriting, merge the specific defines with the
129       // containing defines
130       merged = UndefineArgument.merge(defs[i].getActiveDefines(), merged);
131     }
132     final StringBuffer buf = new StringBuffer(30);
133     for (final UndefineArgument current : merged) {
134       buf.setLength(0);
135       if (current.isDefine()) {
136         getDefineSwitch(buf, current.getName(), current.getValue());
137       } else {
138         getUndefineSwitch(buf, current.getName());
139       }
140       args.addElement(buf.toString());
141     }
142   }
143 
144   @Override
145   public String[] getOutputFileNames(final String inputFile, final VersionInfo versionInfo) {
146     //
147     // if a recognized input file
148     //
149     if (bid(inputFile) > 1) {
150       final String baseName = getBaseOutputName(inputFile);
151       final File standardisedFile = new File(inputFile);
152       try {
153         return new String[] {
154           baseName + FilenameUtils.EXTENSION_SEPARATOR + Integer.toHexString(standardisedFile.getCanonicalPath().hashCode()) + getOutputSuffix()
155         };
156       } catch (IOException e) {
157         throw new BuildException("Source file not found", e);
158       }
159     }
160     return new String[0];
161   }
162 
163   /**
164    * Compiles a source file.
165    * 
166    */
167   public void compile(final CCTask task, final File outputDir, final String[] sourceFiles, String[] args,
168       final String[] endArgs, final boolean relentless, final CommandLineCompilerConfiguration config,
169       final ProgressMonitor monitor) throws BuildException {
170     BuildException exc = null;
171     //
172     // determine length of executable name and args
173     //
174     String command = getCommandWithPath(config);
175     if (config.isUseCcache()) {
176       // Replace the command with "ccache" and push the old compiler
177       // command into the args.
178       final String compilerCommand = command;
179       command = CCACHE_CMD;
180       args = ObjectArrays.concat(compilerCommand, args);
181     }
182     int baseLength = command.length() + args.length + endArgs.length;
183     if (this.libtool) {
184       baseLength += 8;
185     }
186     for (final String arg : args) {
187       baseLength += arg.length();
188     }
189     for (final String endArg : endArgs) {
190       baseLength += endArg.length();
191     }
192     if (baseLength > getMaximumCommandLength()) {
193       throw new BuildException("Command line is over maximum length without specifying source file");
194     }
195     //
196     // typically either 1 or Integer.MAX_VALUE
197     //
198     final int maxInputFilesPerCommand = getMaximumInputFilesPerCommand();
199     final int argumentCountPerInputFile = getArgumentCountPerInputFile();
200     for (int sourceIndex = 0; sourceIndex < sourceFiles.length;) {
201       int cmdLength = baseLength;
202       int firstFileNextExec;
203       for (firstFileNextExec = sourceIndex; firstFileNextExec < sourceFiles.length
204           && firstFileNextExec - sourceIndex < maxInputFilesPerCommand; firstFileNextExec++) {
205         cmdLength += getTotalArgumentLengthForInputFile(outputDir, sourceFiles[firstFileNextExec]);
206         if (cmdLength >= getMaximumCommandLength()) {
207           break;
208         }
209       }
210       if (firstFileNextExec == sourceIndex) {
211         throw new BuildException("Extremely long file name, can't fit on command line");
212       }
213 
214       ArrayList<String> commandlinePrefix = new ArrayList<>();
215       if (this.libtool) {
216         commandlinePrefix.add("libtool");
217       }
218       commandlinePrefix.add(command);
219       Collections.addAll(commandlinePrefix, args);
220 
221       int retval = 0;
222       for (int j = sourceIndex; j < firstFileNextExec; j++) {
223         ArrayList<String> commandlineSuffix = new ArrayList<>();
224 
225         for (int k = 0; k < argumentCountPerInputFile; k++) {
226           commandlineSuffix.add(getInputFileArgument(outputDir, sourceFiles[j], k));
227         }
228         Collections.addAll(commandlineSuffix, endArgs);
229 
230         ArrayList<String> commandline = new ArrayList<>(commandlinePrefix);
231         commandline.addAll(commandlineSuffix);
232         final int ret = runCommand(task, workDir,
233             commandline.toArray(new String[commandline.size()]));
234         if (ret != 0) { retval = ret; }
235       }
236       if (monitor != null) {
237         final String[] fileNames = new String[firstFileNextExec - sourceIndex];
238 
239         System.arraycopy(sourceFiles, sourceIndex + 0, fileNames, 0, fileNames.length);
240         monitor.progress(fileNames);
241       }
242       //
243       // if the process returned a failure code and
244       // we aren't holding an exception from an earlier
245       // interation
246       if (retval != 0 && exc == null) {
247         //
248         // construct the exception
249         //
250         exc = new BuildException(getCommandWithPath(config) + " failed with return code " + retval, task.getLocation());
251 
252         //
253         // and throw it now unless we are relentless
254         //
255         if (!relentless) {
256           throw exc;
257         }
258       }
259       sourceIndex = firstFileNextExec;
260     }
261     //
262     // if the compiler returned a failure value earlier
263     // then throw an exception
264     if (exc != null) {
265       throw exc;
266     }
267   }
268 
269   @Override
270   protected CompilerConfiguration createConfiguration(final CCTask task, final LinkType linkType,
271       final ProcessorDef[] baseDefs, final CompilerDef specificDef, final TargetDef targetPlatform,
272       final VersionInfo versionInfo) {
273 
274     this.prefix = specificDef.getCompilerPrefix();
275     this.objDir = task.getObjdir();
276     final Vector<String> args = new Vector<>();
277     final CompilerDef[] defaultProviders = new CompilerDef[baseDefs.length + 1];
278     for (int i = 0; i < baseDefs.length; i++) {
279       defaultProviders[i + 1] = (CompilerDef) baseDefs[i];
280     }
281     defaultProviders[0] = specificDef;
282     final Vector<CommandLineArgument> cmdArgs = new Vector<>();
283 
284     //
285     // add command line arguments inherited from <cc> element
286     // any "extends" and finally the specific CompilerDef
287     CommandLineArgument[] commandArgs;
288     for (int i = defaultProviders.length - 1; i >= 0; i--) {
289       commandArgs = defaultProviders[i].getActiveProcessorArgs();
290       for (final CommandLineArgument commandArg : commandArgs) {
291         if (commandArg.getLocation() == 0) {
292           String arg = commandArg.getValue();
293           if (isWindows() && arg.matches(".*[ \"].*")) {
294             // Work around inconsistent quoting by Ant
295             arg = "\"" + arg.replaceAll("[\\\\\"]", "\\\\$0") + "\"";
296           }
297           args.addElement(arg);
298         } else {
299           cmdArgs.addElement(commandArg);
300         }
301       }
302     }
303     final Vector<ProcessorParam> params = new Vector<>();
304     //
305     // add command line arguments inherited from <cc> element
306     // any "extends" and finally the specific CompilerDef
307     ProcessorParam[] paramArray;
308     for (int i = defaultProviders.length - 1; i >= 0; i--) {
309       paramArray = defaultProviders[i].getActiveProcessorParams();
310       Collections.addAll(params, paramArray);
311     }
312     paramArray = params.toArray(new ProcessorParam[params.size()]);
313 
314     if (specificDef.isClearDefaultOptions() == false) {
315       final boolean multithreaded = specificDef.getMultithreaded(defaultProviders, 1);
316       final boolean debug = specificDef.getDebug(baseDefs, 0);
317       final boolean exceptions = specificDef.getExceptions(defaultProviders, 1);
318       final Boolean rtti = specificDef.getRtti(defaultProviders, 1);
319       final OptimizationEnum optimization = specificDef.getOptimization(defaultProviders, 1);
320       this.addImpliedArgs(args, debug, multithreaded, exceptions, linkType, rtti, optimization);
321     }
322 
323     //
324     // add all appropriate defines and undefines
325     //
326     buildDefineArguments(defaultProviders, args);
327     final int warnings = specificDef.getWarnings(defaultProviders, 0);
328     addWarningSwitch(args, warnings);
329     Enumeration<CommandLineArgument> argEnum = cmdArgs.elements();
330     int endCount = 0;
331     while (argEnum.hasMoreElements()) {
332       final CommandLineArgument arg = argEnum.nextElement();
333       switch (arg.getLocation()) {
334         case 1:
335           args.addElement(arg.getValue());
336           break;
337         case 2:
338           endCount++;
339           break;
340       }
341     }
342     final String[] endArgs = new String[endCount];
343     argEnum = cmdArgs.elements();
344     int index = 0;
345     while (argEnum.hasMoreElements()) {
346       final CommandLineArgument arg = argEnum.nextElement();
347       if (arg.getLocation() == 2) {
348         endArgs[index++] = arg.getValue();
349       }
350     }
351     //
352     // Want to have distinct set of arguments with relative
353     // path names for includes that are used to build
354     // the configuration identifier
355     //
356     final Vector<String> relativeArgs = (Vector) args.clone();
357     //
358     // add all active include and sysincludes
359     //
360     final StringBuffer includePathIdentifier = new StringBuffer();
361     final File baseDir = specificDef.getProject().getBaseDir();
362     String baseDirPath;
363     try {
364       baseDirPath = baseDir.getCanonicalPath();
365     } catch (final IOException ex) {
366       baseDirPath = baseDir.toString();
367     }
368     final Vector<String> includePath = new Vector<>();
369     final Vector<String> sysIncludePath = new Vector<>();
370     for (int i = defaultProviders.length - 1; i >= 0; i--) {
371       String[] incPath = defaultProviders[i].getActiveIncludePaths();
372       for (final String element : incPath) {
373         includePath.addElement(element);
374       }
375       incPath = defaultProviders[i].getActiveSysIncludePaths();
376       for (final String element : incPath) {
377         sysIncludePath.addElement(element);
378       }
379     }
380     final File[] incPath = new File[includePath.size()];
381     for (int i = 0; i < includePath.size(); i++) {
382       incPath[i] = new File(includePath.elementAt(i));
383     }
384     final File[] sysIncPath = new File[sysIncludePath.size()];
385     for (int i = 0; i < sysIncludePath.size(); i++) {
386       sysIncPath[i] = new File(sysIncludePath.elementAt(i));
387     }
388     addIncludes(baseDirPath, incPath, args, relativeArgs, includePathIdentifier, false);
389     addIncludes(baseDirPath, sysIncPath, args, null, null, true);
390     final StringBuffer buf = new StringBuffer(getIdentifier());
391     for (int i = 0; i < relativeArgs.size(); i++) {
392       buf.append(' ');
393       buf.append(relativeArgs.elementAt(i));
394     }
395     for (final String endArg : endArgs) {
396       buf.append(' ');
397       buf.append(endArg);
398     }
399     final String configId = buf.toString();
400     final String[] argArray = new String[args.size()];
401     args.copyInto(argArray);
402     final boolean rebuild = specificDef.getRebuild(baseDefs, 0);
403     final File[] envIncludePath = getEnvironmentIncludePath();
404     final String path = specificDef.getToolPath();
405 
406     CommandLineCompiler compiler = this;
407     Environment environment = specificDef.getEnv();
408     if (environment == null) {
409       for (final ProcessorDef baseDef : baseDefs) {
410         environment = baseDef.getEnv();
411         if (environment != null) {
412           compiler = (CommandLineCompiler) compiler.changeEnvironment(baseDef.isNewEnvironment(), environment);
413         }
414       }
415     } else {
416       compiler = (CommandLineCompiler) compiler.changeEnvironment(specificDef.isNewEnvironment(), environment);
417     }
418     return new CommandLineCompilerConfiguration(compiler, configId, incPath, sysIncPath, envIncludePath,
419         includePathIdentifier.toString(), argArray, paramArray, rebuild, endArgs, path, specificDef.getCcache());
420   }
421 
422   protected int getArgumentCountPerInputFile() {
423     return 1;
424   }
425 
426   protected final String getCommand() {
427     if (this.prefix != null && (!this.prefix.isEmpty())) {
428       return this.prefix + this.command;
429     } else {
430       return this.command;
431     }
432   }
433 
434   public String getCommandWithPath(final CommandLineCompilerConfiguration config) {
435     if (config.getCommandPath() != null) {
436       final File command = new File(config.getCommandPath(), this.getCommand());
437       try {
438         return command.getCanonicalPath();
439       } catch (final IOException e) {
440         e.printStackTrace();
441         return command.getAbsolutePath();
442       }
443     } else {
444       return this.getCommand();
445     }
446   }
447 
448   abstract protected void getDefineSwitch(StringBuffer buffer, String define, String value);
449 
450   protected abstract File[] getEnvironmentIncludePath();
451 
452   @Override
453   public String getIdentifier() {
454     if (this.identifier == null) {
455       if (this.identifierArg == null) {
456         this.identifier = getIdentifier(new String[] {
457           this.getCommand()
458         }, this.getCommand());
459       } else {
460         this.identifier = getIdentifier(new String[] {
461           this.getCommand(), this.identifierArg
462         }, this.getCommand());
463       }
464     }
465     return this.identifier;
466   }
467 
468   abstract protected String getIncludeDirSwitch(String source);
469 
470   /**
471    * Added by Darren Sargent 22Oct2008 Returns the include dir switch value.
472    * Default implementation doesn't treat system includes specially, for
473    * compilers which don't care.
474    * 
475    * @param source
476    *          the given source value.
477    * @param isSystem
478    *          "true" if this is a system include path
479    * 
480    * @return the include dir switch value.
481    */
482   protected String getIncludeDirSwitch(final String source, final boolean isSystem) {
483     return getIncludeDirSwitch(source);
484   }
485 
486   protected String getInputFileArgument(final File outputDir, final String filename, final int index) {
487     //
488     // if there is an embedded space,
489     // must enclose in quotes
490     String relative="";
491     String inputFile;
492     try {
493       relative = FileUtils.getRelativePath(workDir, new File(filename));
494     } catch (Exception ex) {
495     }
496     if (relative.isEmpty()) {
497       inputFile = filename;
498     } else {
499       inputFile = relative;
500     }
501     if (inputFile.indexOf(' ') >= 0) {
502       final String buf = "\"" + inputFile +
503           "\"";
504       return buf;
505     }
506     return inputFile;
507   }
508 
509   protected final boolean getLibtool() {
510     return this.libtool;
511   }
512 
513   /**
514    * Obtains the same compiler, but with libtool set
515    * 
516    * Default behavior is to ignore libtool
517    */
518   public final CommandLineCompiler getLibtoolCompiler() {
519     if (this.libtoolCompiler != null) {
520       return this.libtoolCompiler;
521     }
522     return this;
523   }
524 
525   abstract public int getMaximumCommandLength();
526 
527   protected int getMaximumInputFilesPerCommand() {
528     return Integer.MAX_VALUE;
529   }
530 
531   /**
532    * Get total command line length due to the input file.
533    * 
534    * @param outputDir
535    *          File output directory
536    * @param inputFile
537    *          String input file
538    * @return int characters added to command line for the input file.
539    */
540   protected int getTotalArgumentLengthForInputFile(final File outputDir, final String inputFile) {
541     final int argumentCountPerInputFile = getArgumentCountPerInputFile();
542     int len=0;
543     for (int k = 0; k < argumentCountPerInputFile; k++) {
544       len+=getInputFileArgument(outputDir, inputFile, k).length();
545     }
546     return len + argumentCountPerInputFile; // argumentCountPerInputFile added for spaces
547   }
548 
549   abstract protected void getUndefineSwitch(StringBuffer buffer, String define);
550 
551   /**
552    * This method is exposed so test classes can overload and test the
553    * arguments without actually spawning the compiler
554    */
555   protected int runCommand(final CCTask task, final File workingDir, final String[] cmdline) throws BuildException {
556     return CUtil.runCommand(task, workingDir, cmdline, this.newEnvironment, this.env);
557   }
558 
559   protected final void setCommand(final String command) {
560     this.command = command;
561   }
562 }