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.FileWriter;
24  import java.io.IOException;
25  import java.util.*;
26  
27  import org.apache.tools.ant.BuildException;
28  import org.apache.tools.ant.types.Environment;
29  
30  import com.github.maven_nar.cpptasks.CCTask;
31  import com.github.maven_nar.cpptasks.CUtil;
32  import com.github.maven_nar.cpptasks.LinkerDef;
33  import com.github.maven_nar.cpptasks.ProcessorDef;
34  import com.github.maven_nar.cpptasks.ProcessorParam;
35  import com.github.maven_nar.cpptasks.TargetDef;
36  import com.github.maven_nar.cpptasks.VersionInfo;
37  import com.github.maven_nar.cpptasks.types.CommandLineArgument;
38  import com.github.maven_nar.cpptasks.types.LibrarySet;
39  
40  /**
41   * An abstract Linker implementation that performs the link via an external
42   * command.
43   *
44   * @author Adam Murdoch
45   */
46  public abstract class CommandLineLinker extends AbstractLinker {
47    private String command;
48    private String prefix;
49    private Environment env = null;
50    private String identifier;
51    private final String identifierArg;
52    private final boolean isLibtool;
53    private String[] librarySets;
54    private final CommandLineLinker libtoolLinker;
55    private final boolean newEnvironment = false;
56    private final String outputSuffix;
57  
58    // FREEHEP
59    private final int maxPathLength = 250;
60  
61    /** Creates a comand line linker invocation */
62    public CommandLineLinker(final String command, final String identifierArg, final String[] extensions,
63        final String[] ignoredExtensions, final String outputSuffix, final boolean isLibtool,
64        final CommandLineLinker libtoolLinker) {
65      super(extensions, ignoredExtensions);
66      this.command = command;
67      this.identifierArg = identifierArg;
68      this.outputSuffix = outputSuffix;
69      this.isLibtool = isLibtool;
70      this.libtoolLinker = libtoolLinker;
71    }
72  
73    protected void addBase(final CCTask task, final long base, final Vector<String> args) {
74      // NB: Do nothing by default.
75    }
76  
77    protected void addEntry(final CCTask task, final String entry, final Vector<String> args) {
78      // NB: Do nothing by default.
79    }
80  
81    protected void addFixed(final CCTask task, final Boolean fixed, final Vector<String> args) {
82      // NB: Do nothing by default.
83    }
84  
85    protected void addImpliedArgs(final CCTask task, final boolean debug, final LinkType linkType,
86        final Vector<String> args) {
87      // NB: Do nothing by default.
88    }
89  
90    protected void addIncremental(final CCTask task, final boolean incremental, final Vector<String> args) {
91      // NB: Do nothing by default.
92    }
93  
94    protected void addLibraryDirectory(final File libraryDirectory, final Vector<String> preargs) {
95      try {
96        if (libraryDirectory != null && libraryDirectory.exists()) {
97          final File currentDir = new File(".").getParentFile();
98          String path = libraryDirectory.getCanonicalPath();
99          if (currentDir != null) {
100           final String currentPath = currentDir.getCanonicalPath();
101           path = CUtil.getRelativePath(currentPath, libraryDirectory);
102         }
103         addLibraryPath(preargs, path);
104       }
105     } catch (final IOException e) {
106       throw new RuntimeException("Unable to add library path: " + libraryDirectory);
107     }
108   }
109 
110   protected void addLibraryPath(final Vector<String> preargs, final String path) {
111   }
112 
113   //
114   // Windows processors handle these through file list
115   //
116   protected String[] addLibrarySets(final CCTask task, final LibrarySet[] libsets, final Vector<String> preargs,
117       final Vector<String> midargs, final Vector<String> endargs) {
118     return null;
119   }
120 
121   protected void addMap(final CCTask task, final boolean map, final Vector<String> args) {
122     // NB: Do nothing by default.
123   }
124 
125   protected void addStack(final CCTask task, final int stack, final Vector<String> args) {
126     // NB: Do nothing by default.
127   }
128 
129   @Override
130   protected LinkerConfiguration createConfiguration(final CCTask task, final LinkType linkType,
131       final ProcessorDef[] baseDefs, final LinkerDef specificDef, final TargetDef targetPlatform,
132       final VersionInfo versionInfo) {
133 
134     final Vector<String> preargs = new Vector<>();
135     final Vector<String> midargs = new Vector<>();
136     final Vector<String> endargs = new Vector<>();
137     final Vector<String>[] args = new Vector[] {
138         preargs, midargs, endargs
139     };
140 
141     this.prefix  = specificDef.getLinkerPrefix();
142 
143     final LinkerDef[] defaultProviders = new LinkerDef[baseDefs.length + 1];
144     defaultProviders[0] = specificDef;
145     for (int i = 0; i < baseDefs.length; i++) {
146       defaultProviders[i + 1] = (LinkerDef) baseDefs[i];
147     }
148 
149     //
150     // add command line arguments inherited from <cc> element
151     // any "extends" and finally the specific CompilerDef
152     CommandLineArgument[] commandArgs;
153     for (int i = defaultProviders.length - 1; i >= 0; i--) {
154       final LinkerDef linkerDef = defaultProviders[i];
155       commandArgs = linkerDef.getActiveProcessorArgs();
156       for (final CommandLineArgument commandArg : commandArgs) {
157         args[commandArg.getLocation()].addElement(commandArg.getValue());
158       }
159     }
160 
161     final Set<File> libraryDirectories = new LinkedHashSet<>();
162     for (int i = defaultProviders.length - 1; i >= 0; i--) {
163       final LinkerDef linkerDef = defaultProviders[i];
164       for (final File libraryDirectory : linkerDef.getLibraryDirectories()) {
165         if (libraryDirectories.add(libraryDirectory)) {
166           addLibraryDirectory(libraryDirectory, preargs);
167         }
168       }
169     }
170 
171     final Vector<ProcessorParam> params = new Vector<>();
172     //
173     // add command line arguments inherited from <cc> element
174     // any "extends" and finally the specific CompilerDef
175     ProcessorParam[] paramArray;
176     for (int i = defaultProviders.length - 1; i >= 0; i--) {
177       paramArray = defaultProviders[i].getActiveProcessorParams();
178       Collections.addAll(params, paramArray);
179     }
180 
181     paramArray = params.toArray(new ProcessorParam[params.size()]);
182 
183     final boolean debug = specificDef.getDebug(baseDefs, 0);
184 
185     final String startupObject = getStartupObject(linkType);
186 
187     addImpliedArgs(task, debug, linkType, preargs);
188     addIncremental(task, specificDef.getIncremental(defaultProviders, 1), preargs);
189     addFixed(task, specificDef.getFixed(defaultProviders, 1), preargs);
190     addMap(task, specificDef.getMap(defaultProviders, 1), preargs);
191     addBase(task, specificDef.getBase(defaultProviders, 1), preargs);
192     addStack(task, specificDef.getStack(defaultProviders, 1), preargs);
193     addEntry(task, specificDef.getEntry(defaultProviders, 1), preargs);
194 
195     String[] libnames = null;
196     final LibrarySet[] libsets = specificDef.getActiveLibrarySets(defaultProviders, 1);
197     // FREEHEP call at all times
198     // if (libsets.length > 0) {
199     libnames = addLibrarySets(task, libsets, preargs, midargs, endargs);
200     // }
201 
202     final StringBuffer buf = new StringBuffer(getIdentifier());
203     for (int i = 0; i < 3; i++) {
204       final Enumeration<String> argenum = args[i].elements();
205       while (argenum.hasMoreElements()) {
206         buf.append(' ');
207         buf.append(argenum.nextElement());
208       }
209     }
210     final String configId = buf.toString();
211 
212     final String[][] options = new String[][] {
213         new String[args[0].size() + args[1].size()], new String[args[2].size()]
214     };
215     args[0].copyInto(options[0]);
216     final int offset = args[0].size();
217     for (int i = 0; i < args[1].size(); i++) {
218       options[0][i + offset] = args[1].elementAt(i);
219     }
220     args[2].copyInto(options[1]);
221 
222     // if this linker doesn't have an env, and there is a more generically
223     // definition for environment, use it.
224     if (null != specificDef.getEnv() && null == this.env) {
225       this.env = specificDef.getEnv();
226     }
227     for (final ProcessorDef processorDef : baseDefs) {
228       final Environment environment = processorDef.getEnv();
229       if (null != environment && null == this.env) {
230         this.env = environment;
231       }
232     }
233     final boolean rebuild = specificDef.getRebuild(baseDefs, 0);
234     final boolean map = specificDef.getMap(defaultProviders, 1);
235     final String toolPath = specificDef.getToolPath();
236 
237     // task.log("libnames:"+libnames.length, Project.MSG_VERBOSE);
238     return new CommandLineLinkerConfiguration(this, configId, options, paramArray, rebuild, map, debug, libnames,
239         startupObject, toolPath);
240   }
241 
242   /**
243    * Allows drived linker to decorate linker option.
244    * Override by GccLinker to prepend a "-Wl," to
245    * pass option to through gcc to linker.
246    *
247    * @param buf
248    *          buffer that may be used and abused in the decoration process,
249    *          must not be null.
250    * @param arg
251    *          linker argument
252    */
253   protected String decorateLinkerOption(final StringBuffer buf, final String arg) {
254     return arg;
255   }
256 
257   protected final String getCommand() {
258     if (this.prefix != null && (!this.prefix.isEmpty())) {
259       return this.prefix + this.command;
260     } else {
261       return this.command;
262     }
263   }
264 
265   protected abstract String getCommandFileSwitch(String commandFile);
266 
267   public String getCommandWithPath(final CommandLineLinkerConfiguration config) {
268     if (config.getCommandPath() != null) {
269       final File command = new File(config.getCommandPath(), this.getCommand());
270       try {
271         return command.getCanonicalPath();
272       } catch (final IOException e) {
273         e.printStackTrace();
274         return command.getAbsolutePath();
275       }
276     } else {
277       return this.getCommand();
278     }
279   }
280 
281   @Override
282   public String getIdentifier() {
283     if (this.identifier == null) {
284       if (this.identifierArg == null) {
285         this.identifier = getIdentifier(new String[] {
286           this.getCommand()
287         }, this.getCommand());
288       } else {
289         this.identifier = getIdentifier(new String[] {
290           this.getCommand(), this.identifierArg
291         }, this.getCommand());
292       }
293     }
294     return this.identifier;
295   }
296 
297   public final CommandLineLinker getLibtoolLinker() {
298     if (this.libtoolLinker != null) {
299       return this.libtoolLinker;
300     }
301     return this;
302   }
303 
304   protected abstract int getMaximumCommandLength();
305 
306   @Override
307   public String[] getOutputFileNames(final String baseName, final VersionInfo versionInfo) {
308     return new String[] {
309       baseName + this.outputSuffix
310     };
311   }
312 
313   protected String[] getOutputFileSwitch(final CCTask task, final String outputFile) {
314     // FREEHEP BEGIN
315     if (isWindows() && outputFile.length() > this.maxPathLength) {
316       throw new BuildException("Absolute path too long, " + outputFile.length() + " > " + this.maxPathLength + ": '"
317           + outputFile);
318     }
319     // FREEHEP END
320     return getOutputFileSwitch(outputFile);
321   }
322 
323   protected abstract String[] getOutputFileSwitch(String outputFile);
324 
325   protected String getStartupObject(final LinkType linkType) {
326     return null;
327   }
328 
329   /**
330    * Performs a link using a command line linker
331    *
332    */
333   public void link(final CCTask task, final File outputFile, final String[] sourceFiles,
334       final CommandLineLinkerConfiguration config) throws BuildException {
335     final File parentDir = new File(outputFile.getParent());
336     String parentPath;
337     try {
338       parentPath = parentDir.getCanonicalPath();
339     } catch (final IOException ex) {
340       parentPath = parentDir.getAbsolutePath();
341     }
342     String[] execArgs = prepareArguments(task, parentPath, outputFile.getName(), sourceFiles, config);
343     int commandLength = 0;
344     for (final String execArg : execArgs) {
345       commandLength += execArg.length() + 1;
346     }
347 
348     //
349     // if command length exceeds maximum
350     // then create a temporary
351     // file containing everything but the command name
352     if (commandLength >= this.getMaximumCommandLength()) {
353       try {
354         execArgs = prepareResponseFile(outputFile, execArgs);
355       } catch (final IOException ex) {
356         throw new BuildException(ex);
357       }
358     }
359 
360     final int retval = runCommand(task, parentDir, execArgs);
361     //
362     // if the process returned a failure code then
363     // throw an BuildException
364     //
365     if (retval != 0) {
366       //
367       // construct the exception
368       //
369       throw new BuildException(getCommandWithPath(config) + " failed with return code " + retval, task.getLocation());
370     }
371 
372   }
373 
374   /**
375    * Prepares argument list for exec command. Will return null
376    * if command line would exceed allowable command line buffer.
377    *
378    * @param task
379    *          compilation task.
380    * @param outputFile
381    *          linker output file
382    * @param sourceFiles
383    *          linker input files (.obj, .o, .res)
384    * @param config
385    *          linker configuration
386    * @return arguments for runTask
387    */
388   protected String[] prepareArguments(final CCTask task, final String outputDir, final String outputFile,
389       final String[] sourceFiles, final CommandLineLinkerConfiguration config) {
390 
391     final String[] preargs = config.getPreArguments();
392     final String[] endargs = config.getEndArguments();
393     final String outputSwitch[] = getOutputFileSwitch(task, outputFile);
394     int allArgsCount = preargs.length + 1 + outputSwitch.length + sourceFiles.length + endargs.length;
395     if (this.isLibtool) {
396       allArgsCount++;
397     }
398     final String[] allArgs = new String[allArgsCount];
399     int index = 0;
400     if (this.isLibtool) {
401       allArgs[index++] = "libtool";
402     }
403     allArgs[index++] = getCommandWithPath(config);
404     final StringBuffer buf = new StringBuffer();
405 
406     for (final String prearg : preargs) {
407       allArgs[index++] = task.isDecorateLinkerOptions() ? decorateLinkerOption(buf, prearg) : prearg;
408     }
409 
410     for (final String element : outputSwitch) {
411       allArgs[index++] = element;
412     }
413     for (final String sourceFile : sourceFiles) {
414       allArgs[index++] = prepareFilename(buf, outputDir, sourceFile);
415     }
416     for (final String endarg : endargs) {
417       allArgs[index++] = task.isDecorateLinkerOptions() ? decorateLinkerOption(buf, endarg) : endarg;
418     }
419 
420     return allArgs;
421   }
422 
423   /**
424    * Processes filename into argument form
425    *
426    */
427   protected String prepareFilename(final StringBuffer buf, final String outputDir, final String sourceFile) {
428     // FREEHEP BEGIN exit if absolute path is too long. Max length on relative
429     // paths in windows is even shorter.
430     if (isWindows() && sourceFile.length() > this.maxPathLength) {
431       throw new BuildException("Absolute path too long, " + sourceFile.length() + " > " + this.maxPathLength + ": '"
432           + sourceFile);
433     }
434     // FREEHEP END
435     return quoteFilename(buf, sourceFile);
436   }
437 
438   /**
439    * Prepares argument list to execute the linker using a
440    * response file.
441    *
442    * @param outputFile
443    *          linker output file
444    * @param args
445    *          output of prepareArguments
446    * @return arguments for runTask
447    */
448   protected String[] prepareResponseFile(final File outputFile, final String[] args) throws IOException {
449     final String baseName = outputFile.getName();
450     final File commandFile = new File(outputFile.getParent(), baseName + ".rsp");
451     final FileWriter writer = new FileWriter(commandFile);
452     int execArgCount = 1;
453     if (this.isLibtool) {
454       execArgCount++;
455     }
456     final String[] execArgs = new String[execArgCount + 1];
457     System.arraycopy(args, 0, execArgs, 0, execArgCount);
458     execArgs[execArgCount] = getCommandFileSwitch(commandFile.toString());
459     for (int i = execArgCount; i < args.length; i++) {
460       //
461       // if embedded space and not quoted then
462       // quote argument
463       if (args[i].contains(" ") && args[i].charAt(0) != '\"') {
464         writer.write('\"');
465         writer.write(args[i]);
466         writer.write("\"\n");
467       } else {
468         writer.write(args[i]);
469         writer.write('\n');
470       }
471     }
472     writer.close();
473     return execArgs;
474   }
475 
476   protected String quoteFilename(final StringBuffer buf, final String filename) {
477     if (filename.indexOf(' ') >= 0) {
478       buf.setLength(0);
479       buf.append('\"');
480       buf.append(filename);
481       buf.append('\"');
482       return buf.toString();
483     }
484     return filename;
485   }
486 
487   /**
488    * This method is exposed so test classes can overload
489    * and test the arguments without actually spawning the
490    * compiler
491    */
492   protected int runCommand(final CCTask task, final File workingDir, final String[] cmdline) throws BuildException {
493     return CUtil.runCommand(task, workingDir, cmdline, this.newEnvironment, this.env);
494   }
495 
496   protected final void setCommand(final String command) {
497     this.command = command;
498   }
499 
500 }