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;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.*;
25  
26  import org.apache.tools.ant.BuildException;
27  import org.apache.tools.ant.Project;
28  import org.apache.tools.ant.taskdefs.Execute;
29  import org.apache.tools.ant.taskdefs.LogStreamHandler;
30  import org.apache.tools.ant.types.Commandline;
31  import org.apache.tools.ant.types.Environment;
32  import org.apache.tools.ant.util.StringUtils;
33  
34  /**
35   * Some utilities used by the CC and Link tasks.
36   *
37   * @author Adam Murdoch
38   * @author Curt Arnold
39   */
40  public class CUtil {
41    /**
42     * A class that splits a white-space, comma-separated list into a String
43     * array. Used for task attributes.
44     */
45    public static final class StringArrayBuilder {
46      private final String[] _value;
47  
48      public StringArrayBuilder(final String value) {
49        // Split the defines up
50        final StringTokenizer tokens = new StringTokenizer(value, ", ");
51        final Vector vallist = new Vector();
52        while (tokens.hasMoreTokens()) {
53          final String val = tokens.nextToken().trim();
54          if (val.length() == 0) {
55            continue;
56          }
57          vallist.addElement(val);
58        }
59        this._value = new String[vallist.size()];
60        vallist.copyInto(this._value);
61      }
62  
63      public String[] getValue() {
64        return this._value;
65      }
66    }
67  
68    public final static int FILETIME_EPSILON = 500;
69  
70    /**
71     * Adds the elements of the array to the given vector
72     */
73    public static void addAll(final Vector dest, final Object[] src) {
74      if (src == null) {
75        return;
76      }
77      for (final Object element : src) {
78        dest.addElement(element);
79      }
80    }
81  
82    /**
83     * Checks a array of names for non existent or non directory entries and
84     * nulls them out.
85     *
86     * @return Count of non-null elements
87     */
88    public static int checkDirectoryArray(final String[] names) {
89      int count = 0;
90      for (int i = 0; i < names.length; i++) {
91        if (names[i] != null) {
92          final File dir = new File(names[i]);
93          if (dir.exists() && dir.isDirectory()) {
94            count++;
95          } else {
96            names[i] = null;
97          }
98        }
99      }
100     return count;
101   }
102 
103   /**
104    * Extracts the basename of a file, removing the extension, if present
105    */
106   public static String getBasename(final File file) {
107     final String path = file.getPath();
108     // Remove the extension
109     String basename = file.getName();
110     final int pos = basename.lastIndexOf('.');
111     if (pos != -1) {
112       basename = basename.substring(0, pos);
113     }
114     return basename;
115   }
116 
117   /**
118    * Gets the parent directory for the executable file name using the current
119    * directory and system executable path
120    *
121    * @param exeName
122    *          Name of executable such as "cl.exe"
123    * @return parent directory or null if not located
124    */
125   public static File getExecutableLocation(final String exeName) {
126     //
127     // must add current working directory to the
128     // from of the path from the "path" environment variable
129     final File currentDir = new File(System.getProperty("user.dir"));
130     if (new File(currentDir, exeName).exists()) {
131       return currentDir;
132     }
133     final File[] envPath = CUtil.getPathFromEnvironment("PATH", File.pathSeparator);
134     for (final File element : envPath) {
135       if (new File(element, exeName).exists()) {
136         return element;
137       }
138     }
139     return null;
140   }
141 
142   /**
143    * Extracts the parent of a file
144    */
145   public static String getParentPath(final String path) {
146     final int pos = path.lastIndexOf(File.separator);
147     if (pos <= 0) {
148       return null;
149     }
150     return path.substring(0, pos);
151   }
152 
153   /**
154    * Returns an array of File for each existing directory in the specified
155    * environment variable
156    *
157    * @param envVariable
158    *          environment variable name such as "LIB" or "INCLUDE"
159    * @param delim
160    *          delimitor used to separate parts of the path, typically ";"
161    *          or ":"
162    * @return array of File's for each part that is an existing directory
163    */
164   public static File[] getPathFromEnvironment(final String envVariable, final String delim) {
165     // OS/4000 does not support the env command.
166     if (System.getProperty("os.name").equals("OS/400")) {
167       return new File[] {};
168     }
169     final Vector osEnv = Execute.getProcEnvironment();
170     final String match = envVariable.concat("=");
171     for (final Enumeration e = osEnv.elements(); e.hasMoreElements();) {
172       final String entry = ((String) e.nextElement()).trim();
173       if (entry.length() > match.length()) {
174         final String entryFrag = entry.substring(0, match.length());
175         if (entryFrag.equalsIgnoreCase(match)) {
176           final String path = entry.substring(match.length());
177           return parsePath(path, delim);
178         }
179       }
180     }
181     final File[] noPath = new File[0];
182     return noPath;
183   }
184 
185   /**
186    * Returns a relative path for the targetFile relative to the base
187    * directory.
188    *
189    * @param base
190    *          base directory as returned by File.getCanonicalPath()
191    * @param targetFile
192    *          target file
193    * @return relative path of target file. Returns targetFile if there were
194    *         no commonalities between the base and the target
195    *
196    */
197   public static String getRelativePath(final String base, final File targetFile) {
198     try {
199       //
200       // remove trailing file separator
201       //
202       String canonicalBase = base;
203       if (base.charAt(base.length() - 1) != File.separatorChar) {
204         canonicalBase = base + File.separatorChar;
205       }
206       //
207       // get canonical name of target
208       //
209       String canonicalTarget;
210       if (System.getProperty("os.name").equals("OS/400")) {
211         canonicalTarget = targetFile.getPath();
212       } else {
213         canonicalTarget = targetFile.getCanonicalPath();
214       }
215       if (canonicalBase.startsWith(canonicalTarget + File.separatorChar)) {
216         canonicalTarget = canonicalTarget + File.separator;
217       }
218       if (canonicalTarget.equals(canonicalBase)) {
219         return ".";
220       }
221       //
222       // see if the prefixes are the same
223       //
224       if (substringMatch(canonicalBase, 0, 2, "\\\\")) {
225         //
226         // UNC file name, if target file doesn't also start with same
227         // server name, don't go there
228         final int endPrefix = canonicalBase.indexOf('\\', 2);
229         final String prefix1 = canonicalBase.substring(0, endPrefix);
230         final String prefix2 = canonicalTarget.substring(0, endPrefix);
231         if (!prefix1.equals(prefix2)) {
232           return canonicalTarget;
233         }
234       } else {
235         if (substringMatch(canonicalBase, 1, 3, ":\\")) {
236           final int endPrefix = 2;
237           final String prefix1 = canonicalBase.substring(0, endPrefix);
238           final String prefix2 = canonicalTarget.substring(0, endPrefix);
239           if (!prefix1.equals(prefix2)) {
240             return canonicalTarget;
241           }
242         } else {
243           if (canonicalBase.charAt(0) == '/') {
244             if (canonicalTarget.charAt(0) != '/') {
245               return canonicalTarget;
246             }
247           }
248         }
249       }
250       final char separator = File.separatorChar;
251       int lastCommonSeparator = -1;
252       int minLength = canonicalBase.length();
253       if (canonicalTarget.length() < minLength) {
254         minLength = canonicalTarget.length();
255       }
256       //
257       // walk to the shorter of the two paths
258       // finding the last separator they have in common
259       for (int i = 0; i < minLength; i++) {
260         if (canonicalTarget.charAt(i) == canonicalBase.charAt(i)) {
261           if (canonicalTarget.charAt(i) == separator) {
262             lastCommonSeparator = i;
263           }
264         } else {
265           break;
266         }
267       }
268       final StringBuffer relativePath = new StringBuffer(50);
269       //
270       // walk from the first difference to the end of the base
271       // adding "../" for each separator encountered
272       //
273       for (int i = lastCommonSeparator + 1; i < canonicalBase.length(); i++) {
274         if (canonicalBase.charAt(i) == separator) {
275           if (relativePath.length() > 0) {
276             relativePath.append(separator);
277           }
278           relativePath.append("..");
279         }
280       }
281       if (canonicalTarget.length() > lastCommonSeparator + 1) {
282         if (relativePath.length() > 0) {
283           relativePath.append(separator);
284         }
285         relativePath.append(canonicalTarget.substring(lastCommonSeparator + 1));
286       }
287       return relativePath.toString();
288     } catch (final IOException ex) {
289     }
290     return targetFile.toString();
291   }
292 
293   public static boolean isActive(final Project p, final String ifCond, final String unlessCond) throws BuildException {
294     if (ifCond != null) {
295       final String ifValue = p.getProperty(ifCond);
296       if (ifValue == null) {
297         return false;
298       } else {
299         if (ifValue.equals("false") || ifValue.equals("no")) {
300           throw new BuildException("if condition \"" + ifCond + "\" has suspicious value \"" + ifValue);
301         }
302       }
303     }
304     if (unlessCond != null) {
305       final String unlessValue = p.getProperty(unlessCond);
306       if (unlessValue != null) {
307         if (unlessValue.equals("false") || unlessValue.equals("no")) {
308           throw new BuildException("unless condition \"" + unlessCond + "\" has suspicious value \"" + unlessValue);
309         }
310         return false;
311       }
312     }
313     return true;
314   }
315 
316   /**
317    * Determines whether time1 is later than time2
318    * to a degree that file system time truncation is not significant.
319    *
320    * @param time1
321    *          long first time value
322    * @param time2
323    *          long second time value
324    * @return boolean if first time value is later than second time value.
325    *         If the values are within the rounding error of the file system
326    *         return false.
327    */
328   public static boolean isSignificantlyAfter(final long time1, final long time2) {
329     return time1 > time2 + FILETIME_EPSILON;
330   }
331 
332   /**
333    * Determines whether time1 is earlier than time2
334    * to a degree that file system time truncation is not significant.
335    *
336    * @param time1
337    *          long first time value
338    * @param time2
339    *          long second time value
340    * @return boolean if first time value is earlier than second time value.
341    *         If the values are within the rounding error of the file system
342    *         return false.
343    */
344   public static boolean isSignificantlyBefore(final long time1, final long time2) {
345     return time1 + FILETIME_EPSILON < time2;
346   }
347 
348   /**
349    * Determines if source file has a system path,
350    * that is part of the compiler or platform.
351    * 
352    * @param source
353    *          source, may not be null.
354    * @return true is source file appears to be system library
355    *         and its path should be discarded.
356    */
357   public static boolean isSystemPath(final File source) {
358     final String lcPath = source.getAbsolutePath().toLowerCase(java.util.Locale.US);
359     return lcPath.contains("platformsdk") || lcPath.contains("windows kits") || lcPath.contains("microsoft")
360         || Objects.equals(lcPath, "/usr/include")
361         || Objects.equals(lcPath, "/usr/lib") || Objects.equals(lcPath, "/usr/local/include")
362         || Objects.equals(lcPath, "/usr/local/lib");
363   }
364 
365   /**
366    * Parse a string containing directories into an File[]
367    *
368    * @param path
369    *          path string, for example ".;c:\something\include"
370    * @param delim
371    *          delimiter, typically ; or :
372    */
373   public static File[] parsePath(final String path, final String delim) {
374     final Vector libpaths = new Vector();
375     int delimPos = 0;
376     for (int startPos = 0; startPos < path.length(); startPos = delimPos + delim.length()) {
377       delimPos = path.indexOf(delim, startPos);
378       if (delimPos < 0) {
379         delimPos = path.length();
380       }
381       //
382       // don't add an entry for zero-length paths
383       //
384       if (delimPos > startPos) {
385         final String dirName = path.substring(startPos, delimPos);
386         final File dir = new File(dirName);
387         if (dir.exists() && dir.isDirectory()) {
388           libpaths.addElement(dir);
389         }
390       }
391     }
392     final File[] paths = new File[libpaths.size()];
393     libpaths.copyInto(paths);
394     return paths;
395   }
396 
397   /**
398    * This method is exposed so test classes can overload and test the
399    * arguments without actually spawning the compiler
400    */
401   public static int runCommand(final CCTask task, final File workingDir, final String[] cmdline,
402       final boolean newEnvironment, final Environment env) throws BuildException {
403     try {
404       task.log(Commandline.toString(cmdline), task.getCommandLogLevel());
405       final Execute exe = new Execute(new LogStreamHandler(task, Project.MSG_INFO, Project.MSG_ERR));
406       if (System.getProperty("os.name").equals("OS/390")) {
407         exe.setVMLauncher(false);
408       }
409       exe.setAntRun(task.getProject());
410       exe.setCommandline(cmdline);
411       exe.setWorkingDirectory(workingDir);
412       if (env != null) {
413         final String[] environment = env.getVariables();
414         if (environment != null) {
415           for (final String element : environment) {
416             task.log("Setting environment variable: " + element, Project.MSG_VERBOSE);
417           }
418         }
419         exe.setEnvironment(environment);
420       }
421       exe.setNewenvironment(newEnvironment);
422       return exe.execute();
423     } catch (final java.io.IOException exc) {
424       throw new BuildException("Could not launch " + cmdline[0] + ": " + exc, task.getLocation());
425     }
426   }
427 
428   /**
429    * Compares the contents of 2 arrays for equaliy.
430    */
431   public static boolean sameList(final Object[] a, final Object[] b) {
432     if (a == null || b == null || a.length != b.length) {
433       return false;
434     }
435     for (int i = 0; i < a.length; i++) {
436       if (!a[i].equals(b[i])) {
437         return false;
438       }
439     }
440     return true;
441   }
442 
443   /**
444    * Compares the contents of an array and a Vector for equality.
445    */
446   public static boolean sameList(final Vector v, final Object[] a) {
447     if (v == null || a == null || v.size() != a.length) {
448       return false;
449     }
450     for (int i = 0; i < a.length; i++) {
451       final Object o = a[i];
452       if (!o.equals(v.elementAt(i))) {
453         return false;
454       }
455     }
456     return true;
457   }
458 
459   /**
460    * Compares the contents of an array and a Vector for set equality. Assumes
461    * input array and vector are sets (i.e. no duplicate entries)
462    */
463   public static boolean sameSet(final Object[] a, final Vector b) {
464     if (a == null || b == null || a.length != b.size()) {
465       return false;
466     }
467     if (a.length == 0) {
468       return true;
469     }
470     // Convert the array into a set
471     final Hashtable t = new Hashtable();
472     for (final Object element : a) {
473       t.put(element, element);
474     }
475     for (int i = 0; i < b.size(); i++) {
476       final Object o = b.elementAt(i);
477       if (t.remove(o) == null) {
478         return false;
479       }
480     }
481     return t.size() == 0;
482   }
483 
484   private static boolean
485       substringMatch(final String src, final int beginIndex, final int endIndex, final String target) {
486     if (src.length() < endIndex) {
487       return false;
488     }
489     return src.substring(beginIndex, endIndex).equals(target);
490   }
491 
492   /**
493    * Converts a vector to a string array.
494    */
495   public static String[] toArray(final Vector src) {
496     final String[] retval = new String[src.size()];
497     src.copyInto(retval);
498     return retval;
499   }
500 
501   public static String toUnixPath(final String path) {
502     if (File.separatorChar != '/' && path.indexOf(File.separatorChar) != -1) {
503       return StringUtils.replace(path, File.separator, "/");
504     }
505     return path;
506   }
507 
508   public static String toWindowsPath(final String path) {
509     if (File.separatorChar != '\\' && path.indexOf(File.separatorChar) != -1) {
510       return StringUtils.replace(path, File.separator, "\\");
511     }
512     return path;
513   }
514 
515   /**
516    * Replaces any embedded quotes in the string so that the value can be
517    * placed in an attribute in an XML file
518    *
519    * @param attrValue
520    *          value to be expressed
521    * @return equivalent attribute literal
522    *
523    */
524   public static String xmlAttribEncode(final String attrValue) {
525     final StringBuffer buf = new StringBuffer(attrValue);
526     int quotePos;
527 
528     for (quotePos = -1; (quotePos = buf.indexOf("\"", quotePos + 1)) >= 0;) {
529       buf.deleteCharAt(quotePos);
530       buf.insert(quotePos, "&quot;");
531       quotePos += 5;
532     }
533 
534     for (quotePos = -1; (quotePos = buf.indexOf("<", quotePos + 1)) >= 0;) {
535       buf.deleteCharAt(quotePos);
536       buf.insert(quotePos, "&lt;");
537       quotePos += 3;
538     }
539 
540     for (quotePos = -1; (quotePos = buf.indexOf(">", quotePos + 1)) >= 0;) {
541       buf.deleteCharAt(quotePos);
542       buf.insert(quotePos, "&gt;");
543       quotePos += 3;
544     }
545 
546     return buf.toString();
547   }
548 
549 }