/*
*
* Copyright 2004 The Ant-Contrib project
*
* 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.
*/
package net.sf.antcontrib.cpptasks.platforms;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import net.sf.antcontrib.cpptasks.CUtil;
import net.sf.antcontrib.cpptasks.TargetMatcher;
import net.sf.antcontrib.cpptasks.VersionInfo;
import net.sf.antcontrib.cpptasks.compiler.LinkType;
/**
* Platform specific behavior for Microsoft Windows.
*
* @author Curt Arnold
*/
public final class WindowsPlatform {
/**
* Constructor.
*/
private WindowsPlatform() {
}
/**
* Adds source or object files to the bidded fileset to
* support version information.
*
* @param versionInfo version information
* @param linkType link type
* @param isDebug true if debug build
* @param outputFile name of generated executable
* @param objDir directory for generated files
* @param matcher bidded fileset
* @throws IOException if unable to write version resource
*/
public static void addVersionFiles(final VersionInfo versionInfo,
final LinkType linkType,
final File outputFile,
final boolean isDebug,
final File objDir,
final TargetMatcher matcher)
throws IOException {
if (versionInfo == null) {
throw new NullPointerException("versionInfo");
}
if (linkType == null) {
throw new NullPointerException("linkType");
}
if (outputFile == null) {
throw new NullPointerException("outputFile");
}
if (objDir == null) {
throw new NullPointerException("objDir");
}
/**
* Fully resolve version info
*/
VersionInfo mergedInfo = versionInfo.merge();
File versionResource = new File(objDir, "versioninfo.rc");
boolean notChanged = false;
//
// if the resource exists
//
if (versionResource.exists()) {
ByteArrayOutputStream memStream = new ByteArrayOutputStream();
Writer writer = new BufferedWriter(new OutputStreamWriter(memStream));
writeResource(writer, mergedInfo, outputFile, isDebug, linkType);
writer.close();
ByteArrayInputStream proposedResource = new ByteArrayInputStream(
memStream.toByteArray());
InputStream existingResource = new FileInputStream(versionResource);
//
//
//
notChanged = hasSameContent(proposedResource, existingResource);
existingResource.close();
}
//
// if the resource file did not exist or will be changed then
// write the file
//
if (!notChanged) {
Writer writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(versionResource)));
writeResource(writer, mergedInfo, outputFile, isDebug, linkType);
writer.close();
}
if (matcher != null) {
matcher.visit(new File(versionResource.getParent()),
versionResource.getName());
}
}
/**
* Compare two input streams for duplicate content
*
* Naive implementation, but should not be performance issue.
* @param stream1 stream
* @param stream2 stream
* @return true if streams are identical in content
* @throws IOException if error reading streams
*/
private static boolean hasSameContent(final InputStream stream1,
final InputStream stream2)
throws IOException {
int byte1 = -1;
int byte2 = -1;
do {
byte1 = stream1.read();
byte2 = stream2.read();
}
while (byte1 == byte2 && byte1 != -1);
return (byte1 == byte2);
}
/**
* Parse version string into array of four short values.
* @param version String version
* @return short[] four element array
*/
public static short[] parseVersion(final String version) {
short[] values = new short[] {
0, 0, 0, 0};
if (version != null) {
StringBuffer buf = new StringBuffer(version);
int start = 0;
for (int i = 0; i < 4; i++) {
int end = version.indexOf('.', start);
if (end <= 0) {
end = version.length();
for (int j = end; j > start; j--) {
String part = buf.substring(start, end);
try {
values[i] = Short.parseShort(part);
break;
} catch (NumberFormatException ex) {
values[i] = 0;
}
}
break;
} else {
String part = buf.substring(start, end);
try {
values[i] = Short.parseShort(part);
start = end + 1;
} catch (NumberFormatException ex) {
break;
}
}
}
}
return values;
}
/**
* Converts parsed version information into a string representation.
*
* @param buf StringBuffer string buffer to receive version number
* @param version short[] four-element array
*/
private static void encodeVersion(final StringBuffer buf,
final short[] version) {
for (int i = 0; i < 3; i++) {
buf.append(Short.toString(version[i]));
buf.append(',');
}
buf.append(Short.toString(version[3]));
}
/**
* Writes windows resource.
* @param writer writer, may not be nul
* @param versionInfo version information
* @param outputFile executable file
* @param isDebug true if debug
* @param linkType link type
* @throws IOException if error writing resource file
*/
public static void writeResource(final Writer writer,
final VersionInfo versionInfo,
final File outputFile,
final boolean isDebug,
final LinkType linkType) throws IOException {
//writer.write("#include \"windows.h\"\n");
writer.write("VS_VERSION_INFO VERSIONINFO\n");
StringBuffer buf = new StringBuffer("FILEVERSION ");
encodeVersion(buf, parseVersion(versionInfo.getFileversion()));
buf.append("\nPRODUCTVERSION ");
encodeVersion(buf, parseVersion(versionInfo.getProductversion()));
buf.append("\n");
writer.write(buf.toString());
buf.setLength(0);
buf.append("FILEFLAGSMASK 0x1L /* VS_FF_DEBUG */");
Boolean patched = versionInfo.getPatched();
Boolean prerelease = versionInfo.getPrerelease();
if (patched != null) {
buf.append(" | 0x4L /* VS_FF_PATCHED */");
}
if (prerelease != null) {
buf.append(" | 0x2L /* VS_FF_PRERELEASE */");
}
if (versionInfo.getPrivatebuild() != null) {
buf.append(" | 0x8L /* VS_FF_PRIVATEBUILD */");
}
if (versionInfo.getSpecialbuild() != null) {
buf.append(" | 0x20L /* VS_FF_SPECIALBUILD */");
}
buf.append('\n');
writer.write(buf.toString());
buf.setLength(0);
buf.append("FILEFLAGS ");
if (isDebug) {
buf.append("0x1L /* VS_FF_DEBUG */ | ");
}
if (Boolean.TRUE.equals(patched)) {
buf.append("0x4L /* VS_FF_PATCHED */ | ");
}
if (Boolean.TRUE.equals(prerelease)) {
buf.append("0x2L /* VS_FF_PRERELEASE */ | ");
}
if (Boolean.TRUE.equals(versionInfo.getPrivatebuild())) {
buf.append("0x8L /* VS_FF_PRIVATEBUILD */ | ");
}
if (Boolean.TRUE.equals(versionInfo.getSpecialbuild())) {
buf.append("0x20L /* VS_FF_SPECIALBUILD */ | ");
}
if (buf.length() > 10) {
buf.setLength(buf.length() - 3);
buf.append('\n');
} else {
buf.append("0\n");
}
writer.write(buf.toString());
buf.setLength(0);
writer.write("FILEOS 0x40004 /* VOS_NT_WINDOWS32 */\nFILETYPE ");
if (linkType.isExecutable()) {
writer.write("0x1L /* VFT_APP */\n");
} else {
if (linkType.isSharedLibrary()) {
writer.write("0x2L /* VFT_DLL */\n");
} else if (linkType.isStaticLibrary()) {
writer.write("0x7L /* VFT_STATIC_LIB */\n");
} else {
writer.write("0x0L /* VFT_UNKNOWN */\n");
}
}
writer.write("FILESUBTYPE 0x0L\n");
writer.write("BEGIN\n");
writer.write("BLOCK \"StringFileInfo\"\n");
writer.write(" BEGIN\n#ifdef UNICODE\nBLOCK \"040904B0\"\n");
writer.write("#else\nBLOCK \"040904E4\"\n#endif\n");
writer.write("BEGIN\n");
if (versionInfo.getFilecomments() != null) {
writer.write("VALUE \"Comments\", \"");
writer.write(versionInfo.getFilecomments());
writer.write("\\0\"\n");
}
if (versionInfo.getCompanyname() != null) {
writer.write("VALUE \"CompanyName\", \"");
writer.write(versionInfo.getCompanyname());
writer.write("\\0\"\n");
}
if (versionInfo.getFiledescription() != null) {
writer.write("VALUE \"FileDescription\", \"");
writer.write(versionInfo.getFiledescription());
writer.write("\\0\"\n");
}
if (versionInfo.getFileversion() != null) {
writer.write("VALUE \"FileVersion\", \"");
writer.write(versionInfo.getFileversion());
writer.write("\\0\"\n");
}
String baseName = CUtil.getBasename(outputFile);
String internalName = versionInfo.getInternalname();
if (internalName == null) {
internalName = baseName;
}
writer.write("VALUE \"InternalName\", \"");
writer.write(internalName);
writer.write("\\0\"\n");
if (versionInfo.getLegalcopyright() != null) {
writer.write("VALUE \"LegalCopyright\", \"");
writer.write(versionInfo.getLegalcopyright());
writer.write("\\0\"\n");
}
if (versionInfo.getLegaltrademarks() != null) {
writer.write("VALUE \"LegalTrademarks\", \"");
writer.write(versionInfo.getLegaltrademarks());
writer.write("\\0\"\n");
}
writer.write("VALUE \"OriginalFilename\", \"");
writer.write(baseName);
writer.write("\\0\"\n");
if (versionInfo.getPrivatebuild() != null) {
writer.write("VALUE \"PrivateBuild\", \"");
writer.write(versionInfo.getPrivatebuild());
writer.write("\\0\"\n");
}
if (versionInfo.getProductname() != null) {
writer.write("VALUE \"ProductName\", \"");
writer.write(versionInfo.getProductname());
writer.write("\\0\"\n");
}
if (versionInfo.getProductversion() != null) {
writer.write("VALUE \"ProductVersion\", \"");
writer.write(versionInfo.getProductversion());
writer.write("\\0\"\n");
}
if (versionInfo.getSpecialbuild() != null) {
writer.write("VALUE \"SpecialBuild\", \"");
writer.write(versionInfo.getSpecialbuild());
writer.write("\\0\"\n");
}
writer.write("END\n");
writer.write("END\n");
writer.write("BLOCK \"VarFileInfo\"\n");
writer.write("BEGIN\n#ifdef UNICODE\n");
writer.write("VALUE \"Translation\", 0x409, 1200\n");
writer.write("#else\n");
writer.write("VALUE \"Translation\", 0x409, 1252\n");
writer.write("#endif\n");
writer.write("END\n");
writer.write("END\n");
}
}