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.platforms;
21  
22  import java.io.BufferedWriter;
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStreamWriter;
31  import java.io.Writer;
32  
33  import com.github.maven_nar.cpptasks.CUtil;
34  import com.github.maven_nar.cpptasks.TargetMatcher;
35  import com.github.maven_nar.cpptasks.VersionInfo;
36  import com.github.maven_nar.cpptasks.compiler.LinkType;
37  
38  /**
39   * Platform specific behavior for Microsoft Windows.
40   *
41   * @author Curt Arnold
42   */
43  public final class WindowsPlatform {
44  
45    /**
46     * Adds source or object files to the bidded fileset to
47     * support version information.
48     *
49     * @param versionInfo
50     *          version information
51     * @param linkType
52     *          link type
53     * @param isDebug
54     *          true if debug build
55     * @param outputFile
56     *          name of generated executable
57     * @param objDir
58     *          directory for generated files
59     * @param matcher
60     *          bidded fileset
61     * @throws IOException
62     *           if unable to write version resource
63     */
64    public static void addVersionFiles(final VersionInfo versionInfo, final LinkType linkType, final File outputFile,
65        final boolean isDebug, final File objDir, final TargetMatcher matcher) throws IOException {
66      if (versionInfo == null) {
67        throw new NullPointerException("versionInfo");
68      }
69      if (linkType == null) {
70        throw new NullPointerException("linkType");
71      }
72      if (outputFile == null) {
73        throw new NullPointerException("outputFile");
74      }
75      if (objDir == null) {
76        throw new NullPointerException("objDir");
77      }
78  
79      /**
80       * Fully resolve version info
81       */
82      final VersionInfo mergedInfo = versionInfo.merge();
83  
84      final File versionResource = new File(objDir, "versioninfo.rc");
85  
86      boolean notChanged = false;
87      //
88      // if the resource exists
89      //
90      if (versionResource.exists()) {
91        final ByteArrayOutputStream memStream = new ByteArrayOutputStream();
92        final Writer writer = new BufferedWriter(new OutputStreamWriter(memStream));
93        writeResource(writer, mergedInfo, outputFile, isDebug, linkType);
94        writer.close();
95        final ByteArrayInputStream proposedResource = new ByteArrayInputStream(memStream.toByteArray());
96  
97        final InputStream existingResource = new FileInputStream(versionResource);
98        //
99        //
100       //
101       notChanged = hasSameContent(proposedResource, existingResource);
102       existingResource.close();
103     }
104 
105     //
106     // if the resource file did not exist or will be changed then
107     // write the file
108     //
109     if (!notChanged) {
110       final Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(versionResource)));
111       writeResource(writer, mergedInfo, outputFile, isDebug, linkType);
112       writer.close();
113     }
114     if (matcher != null) {
115       matcher.visit(new File(versionResource.getParent()), versionResource.getName());
116     }
117 
118   }
119 
120   /**
121    * Converts parsed version information into a string representation.
122    *
123    * @param buf
124    *          StringBuffer string buffer to receive version number
125    * @param version
126    *          short[] four-element array
127    */
128   private static void encodeVersion(final StringBuffer buf, final short[] version) {
129     for (int i = 0; i < 3; i++) {
130       buf.append(Short.toString(version[i]));
131       buf.append(',');
132     }
133     buf.append(Short.toString(version[3]));
134   }
135 
136   /**
137    * Compare two input streams for duplicate content
138    *
139    * Naive implementation, but should not be performance issue.
140    * 
141    * @param stream1
142    *          stream
143    * @param stream2
144    *          stream
145    * @return true if streams are identical in content
146    * @throws IOException
147    *           if error reading streams
148    */
149   private static boolean hasSameContent(final InputStream stream1, final InputStream stream2) throws IOException {
150     int byte1 = -1;
151     int byte2 = -1;
152     do {
153       byte1 = stream1.read();
154       byte2 = stream2.read();
155 
156     } while (byte1 == byte2 && byte1 != -1);
157     return byte1 == byte2;
158   }
159 
160   /**
161    * Parse version string into array of four short values.
162    * 
163    * @param version
164    *          String version
165    * @return short[] four element array
166    */
167   public static short[] parseVersion(final String version) {
168     final short[] values = new short[] {
169         0, 0, 0, 0
170     };
171     if (version != null) {
172       final StringBuffer buf = new StringBuffer(version);
173       int start = 0;
174       for (int i = 0; i < 4; i++) {
175         int end = version.indexOf('.', start);
176         if (end <= 0) {
177           end = version.length();
178           for (int j = end; j > start; j--) {
179             final String part = buf.substring(start, end);
180             try {
181               values[i] = Short.parseShort(part);
182               break;
183             } catch (final NumberFormatException ex) {
184               values[i] = 0;
185             }
186           }
187           break;
188         } else {
189           final String part = buf.substring(start, end);
190           try {
191             values[i] = Short.parseShort(part);
192             start = end + 1;
193           } catch (final NumberFormatException ex) {
194             break;
195           }
196         }
197       }
198     }
199     return values;
200   }
201 
202   /**
203    * Writes windows resource.
204    * 
205    * @param writer
206    *          writer, may not be nul
207    * @param versionInfo
208    *          version information
209    * @param outputFile
210    *          executable file
211    * @param isDebug
212    *          true if debug
213    * @param linkType
214    *          link type
215    * @throws IOException
216    *           if error writing resource file
217    */
218   public static void writeResource(final Writer writer, final VersionInfo versionInfo, final File outputFile,
219       final boolean isDebug, final LinkType linkType) throws IOException {
220 
221     // writer.write("#include \"windows.h\"\n");
222 
223     writer.write("VS_VERSION_INFO VERSIONINFO\n");
224     final StringBuffer buf = new StringBuffer("FILEVERSION ");
225     encodeVersion(buf, parseVersion(versionInfo.getFileversion()));
226     buf.append("\nPRODUCTVERSION ");
227     encodeVersion(buf, parseVersion(versionInfo.getProductversion()));
228     buf.append("\n");
229     writer.write(buf.toString());
230     buf.setLength(0);
231     buf.append("FILEFLAGSMASK 0x1L /* VS_FF_DEBUG */");
232     final Boolean patched = versionInfo.getPatched();
233     final Boolean prerelease = versionInfo.getPrerelease();
234     if (patched != null) {
235       buf.append(" | 0x4L /* VS_FF_PATCHED */");
236     }
237     if (prerelease != null) {
238       buf.append(" | 0x2L /* VS_FF_PRERELEASE */");
239     }
240     if (versionInfo.getPrivatebuild() != null) {
241       buf.append(" | 0x8L /* VS_FF_PRIVATEBUILD */");
242     }
243     if (versionInfo.getSpecialbuild() != null) {
244       buf.append(" | 0x20L /* VS_FF_SPECIALBUILD */");
245     }
246     buf.append('\n');
247     writer.write(buf.toString());
248     buf.setLength(0);
249     buf.append("FILEFLAGS ");
250 
251     if (isDebug) {
252       buf.append("0x1L /* VS_FF_DEBUG */ | ");
253     }
254     if (Boolean.TRUE.equals(patched)) {
255       buf.append("0x4L /* VS_FF_PATCHED */ | ");
256     }
257     if (Boolean.TRUE.equals(prerelease)) {
258       buf.append("0x2L /* VS_FF_PRERELEASE */ | ");
259     }
260     // FIXME: What are the possible values of private build? Would it be sufficient to check if private build is empty?
261     if (Boolean.TRUE.equals(versionInfo.getPrivatebuild())) {
262       buf.append("0x8L /* VS_FF_PRIVATEBUILD */ | ");
263     }
264     // FIXME: What are the possible values of special build? Would it be sufficient to check if special build is empty?
265     if (Boolean.TRUE.equals(versionInfo.getSpecialbuild())) {
266       buf.append("0x20L /* VS_FF_SPECIALBUILD */ | ");
267     }
268     if (buf.length() > 10) {
269       buf.setLength(buf.length() - 3);
270       buf.append('\n');
271     } else {
272       buf.append("0\n");
273     }
274     writer.write(buf.toString());
275     buf.setLength(0);
276 
277     writer.write("FILEOS 0x40004 /* VOS_NT_WINDOWS32 */\nFILETYPE ");
278     if (linkType.isExecutable()) {
279       writer.write("0x1L /* VFT_APP */\n");
280     } else {
281       if (linkType.isSharedLibrary()) {
282         writer.write("0x2L /* VFT_DLL */\n");
283       } else if (linkType.isStaticLibrary()) {
284         writer.write("0x7L /* VFT_STATIC_LIB */\n");
285       } else {
286         writer.write("0x0L /* VFT_UNKNOWN */\n");
287       }
288     }
289     writer.write("FILESUBTYPE 0x0L\n");
290     writer.write("BEGIN\n");
291     writer.write("BLOCK \"StringFileInfo\"\n");
292     writer.write("   BEGIN\n#ifdef UNICODE\nBLOCK \"040904B0\"\n");
293     writer.write("#else\nBLOCK \"040904E4\"\n#endif\n");
294     writer.write("BEGIN\n");
295     if (versionInfo.getFilecomments() != null) {
296       writer.write("VALUE \"Comments\", \"");
297       writer.write(versionInfo.getFilecomments());
298       writer.write("\\0\"\n");
299     }
300     if (versionInfo.getCompanyname() != null) {
301       writer.write("VALUE \"CompanyName\", \"");
302       writer.write(versionInfo.getCompanyname());
303       writer.write("\\0\"\n");
304     }
305     if (versionInfo.getFiledescription() != null) {
306       writer.write("VALUE \"FileDescription\", \"");
307       writer.write(versionInfo.getFiledescription());
308       writer.write("\\0\"\n");
309     }
310     if (versionInfo.getFileversion() != null) {
311       writer.write("VALUE \"FileVersion\", \"");
312       writer.write(versionInfo.getFileversion());
313       writer.write("\\0\"\n");
314     }
315     final String baseName = CUtil.getBasename(outputFile);
316     String internalName = versionInfo.getInternalname();
317     if (internalName == null) {
318       internalName = baseName;
319     }
320     writer.write("VALUE \"InternalName\", \"");
321     writer.write(internalName);
322     writer.write("\\0\"\n");
323     if (versionInfo.getLegalcopyright() != null) {
324       writer.write("VALUE \"LegalCopyright\", \"");
325       writer.write(versionInfo.getLegalcopyright());
326       writer.write("\\0\"\n");
327     }
328     if (versionInfo.getLegaltrademarks() != null) {
329       writer.write("VALUE \"LegalTrademarks\", \"");
330       writer.write(versionInfo.getLegaltrademarks());
331       writer.write("\\0\"\n");
332     }
333     writer.write("VALUE \"OriginalFilename\", \"");
334     writer.write(baseName);
335     writer.write("\\0\"\n");
336     if (versionInfo.getPrivatebuild() != null) {
337       writer.write("VALUE \"PrivateBuild\", \"");
338       writer.write(versionInfo.getPrivatebuild());
339       writer.write("\\0\"\n");
340     }
341     if (versionInfo.getProductname() != null) {
342       writer.write("VALUE \"ProductName\", \"");
343       writer.write(versionInfo.getProductname());
344       writer.write("\\0\"\n");
345     }
346     if (versionInfo.getProductversion() != null) {
347       writer.write("VALUE \"ProductVersion\", \"");
348       writer.write(versionInfo.getProductversion());
349       writer.write("\\0\"\n");
350     }
351     if (versionInfo.getSpecialbuild() != null) {
352       writer.write("VALUE \"SpecialBuild\", \"");
353       writer.write(versionInfo.getSpecialbuild());
354       writer.write("\\0\"\n");
355     }
356     writer.write("END\n");
357     writer.write("END\n");
358 
359     writer.write("BLOCK \"VarFileInfo\"\n");
360     writer.write("BEGIN\n#ifdef UNICODE\n");
361     writer.write("VALUE \"Translation\", 0x409, 1200\n");
362     writer.write("#else\n");
363     writer.write("VALUE \"Translation\", 0x409, 1252\n");
364     writer.write("#endif\n");
365     writer.write("END\n");
366     writer.write("END\n");
367   }
368 
369   /**
370    * Constructor.
371    */
372   private WindowsPlatform() {
373   }
374 
375 }