/* * * Copyright 2004-2008 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.devstudio; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sf.antcontrib.cpptasks.CCTask; import net.sf.antcontrib.cpptasks.CUtil; import net.sf.antcontrib.cpptasks.TargetInfo; import net.sf.antcontrib.cpptasks.compiler.CommandLineCompilerConfiguration; import net.sf.antcontrib.cpptasks.compiler.CommandLineLinkerConfiguration; import net.sf.antcontrib.cpptasks.compiler.ProcessorConfiguration; import net.sf.antcontrib.cpptasks.ide.CommentDef; import net.sf.antcontrib.cpptasks.ide.DependencyDef; import net.sf.antcontrib.cpptasks.ide.ProjectDef; import net.sf.antcontrib.cpptasks.ide.ProjectWriter; import org.apache.tools.ant.BuildException; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * Writes a Visual Studio.NET project file. * * @author curta */ public final class VisualStudioNETProjectWriter implements ProjectWriter { /** * Version of VisualStudio.NET. */ private final String version; /** * Literal to represent a true value. */ private final String trueLiteral; /** * Literal to represent a false value. */ private final String falseLiteral; /** * Constructor. * * @param versionArg String VisualStudio.NET version * @param trueArg literal to represent true, "true" in VC 2005. * @param falseArg literal to represent false, "false" in VC 2005. */ public VisualStudioNETProjectWriter(final String versionArg, final String trueArg, final String falseArg) { if (versionArg == null) { throw new IllegalArgumentException("versionArg"); } if (trueArg == null) { throw new IllegalArgumentException("trueArg"); } if (falseArg == null) { throw new IllegalArgumentException("falseArg"); } this.version = versionArg; this.trueLiteral = trueArg; this.falseLiteral = falseArg; } /** * Gets the configuration type. * * @param task cc task, may not be null. * @return configuration type */ private String getConfigurationType(final CCTask task) { String outputType = task.getOuttype(); String targtype = "2"; // Win32 (x86) Dynamic-Link Library"; if ("executable".equals(outputType)) { targtype = "1"; // "Win32 (x86) Console Application"; } else if ("static".equals(outputType)) { targtype = "4"; //"Win32 (x86) Static Library"; } return targtype; } /** * Get character set for Windows API. * @param compilerConfig compiler configuration, may not be null. * @return "1" is TCHAR is unicode, "0" if TCHAR is multi-byte. */ private String getCharacterSet( final CommandLineCompilerConfiguration compilerConfig) { String[] args = compilerConfig.getPreArguments(); String charset = "0"; for (int i = 0; i < args.length; i++) { if ("/D_UNICODE".equals(args[i]) || "/DUNICODE".equals(args[i])) { charset = "1"; } if ("/D_MBCS".equals(args[i])) { charset = "2"; } } return charset; } /** * Write the start tag of the Configuration element. * @param content serialization content handler. * @param isDebug if true, write a debug configuration. * @param task cc task. * @param compilerConfig compiler configuration. * @throws SAXException thrown if serialization error. */ private void writeConfigurationStartTag(final ContentHandler content, final boolean isDebug, final CCTask task, final CommandLineCompilerConfiguration compilerConfig) throws SAXException { AttributesImpl attributes = new AttributesImpl(); if (isDebug) { addAttribute(attributes, "Name", "Debug|Win32"); addAttribute(attributes, "OutputDirectory", "Debug"); addAttribute(attributes, "IntermediateDirectory", "Debug"); } else { addAttribute(attributes, "Name", "Release|Win32"); addAttribute(attributes, "OutputDirectory", "Release"); addAttribute(attributes, "IntermediateDirectory", "Release"); } addAttribute(attributes, "ConfigurationType", getConfigurationType(task)); addAttribute(attributes, "CharacterSet", getCharacterSet(compilerConfig)); content.startElement(null, "Configuration", "Configuration", attributes); } /** * Get value of Optimization property. * @param compilerConfig compiler configuration, may not be null. * @return value of Optimization property. */ private String getOptimization( final CommandLineCompilerConfiguration compilerConfig) { String[] args = compilerConfig.getPreArguments(); String opt = "0"; for (int i = 0; i < args.length; i++) { if ("/Od".equals(args[i])) { opt = "0"; } if ("/O1".equals(args[i])) { opt = "1"; } if ("/O2".equals(args[i])) { opt = "2"; } if ("/Ox".equals(args[i])) { opt = "3"; } } return opt; } /** * Get value of AdditionalIncludeDirectories property. * @param compilerConfig compiler configuration. * @param baseDir base for relative paths. * @return value of AdditionalIncludeDirectories property. */ private String getAdditionalIncludeDirectories( final String baseDir, final CommandLineCompilerConfiguration compilerConfig) { File[] includePath = compilerConfig.getIncludePath(); StringBuffer includeDirs = new StringBuffer(); // Darren Sargent Feb 10 2010 -- reverted to older code to ensure sys // includes get, erm, included String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("/I")) { includeDirs.append(args[i].substring(2)); includeDirs.append(';'); } } // end Darren if (includeDirs.length() > 0) { includeDirs.setLength(includeDirs.length() - 1); } return includeDirs.toString(); } /** * Get value of PreprocessorDefinitions property. * @param compilerConfig compiler configuration. * @param isDebug true if generating debug configuration. * @return value of PreprocessorDefinitions property. */ private String getPreprocessorDefinitions( final CommandLineCompilerConfiguration compilerConfig, final boolean isDebug) { StringBuffer defines = new StringBuffer(); String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("/D")) { String macro = args[i].substring(2); if (isDebug) { if (macro.equals("NDEBUG")) { macro = "_DEBUG"; } } else { if (macro.equals("_DEBUG")) { macro = "NDEBUG"; } } defines.append(macro); defines.append(";"); } } if (defines.length() > 0) { defines.setLength(defines.length() - 1); } return defines.toString(); } /** * Get value of RuntimeLibrary property. * @param compilerConfig compiler configuration. * @param isDebug true if generating debug configuration. * @return value of RuntimeLibrary property. */ private String getRuntimeLibrary( final CommandLineCompilerConfiguration compilerConfig, final boolean isDebug) { String rtl = null; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("/MT")) { if (isDebug) { rtl = "1"; } else { rtl = "0"; } } else if (args[i].startsWith("/MD")) { if (isDebug) { rtl = "3"; } else { rtl = "2"; } } } return rtl; } /** * Get value of UsePrecompiledHeader property. * @param compilerConfig compiler configuration. * @return value of UsePrecompiledHeader property. */ private String getUsePrecompiledHeader( final CommandLineCompilerConfiguration compilerConfig) { String usePCH = "0"; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/Yc".equals(args[i])) { usePCH = "1"; } if ("/Yu".equals(args[i])) { usePCH = "2"; } } return usePCH; } /** * Get value of PrecompiledHeaderFile property. * @param compilerConfig compiler configuration. * @return value of PrecompiledHeaderFile property. */ private String getPrecompiledHeaderFile( final CommandLineCompilerConfiguration compilerConfig) { String pch = null; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("/Fp")) { pch = args[i].substring(3); } } return pch; } /** * Get value of BasicRuntimeChecks property. * @param compilerConfig compiler configuration. * @return value of BasicRuntimeChecks property. */ private String getBasicRuntimeChecks( final CommandLineCompilerConfiguration compilerConfig) { String checks = "0"; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/RTCs".equals(args[i])) { checks = "1"; } if ("/RTCu".equals(args[i])) { checks = "2"; } if ("/RTC1".equals(args[i]) || "/GZ".equals(args[i])) { checks = "3"; } } return checks; } /** * Get value of WarningLevel property. * @param compilerConfig compiler configuration. * @return value of WarningLevel property. */ private String getWarningLevel( final CommandLineCompilerConfiguration compilerConfig) { String warn = null; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/W0".equals(args[i])) { warn = "0"; } if ("/W1".equals(args[i])) { warn = "1"; } if ("/W2".equals(args[i])) { warn = "2"; } if ("/W3".equals(args[i])) { warn = "3"; } // Added by Darren Sargent, 2/26/2008 if ("/W4".equals(args[i])) { warn = "4"; } // end added } return warn; } /** * Get value of Detect64BitPortabilityProblems property. * @param compilerConfig compiler configuration. * @return value of Detect64BitPortabilityProblems property. */ private String getDetect64BitPortabilityProblems( final CommandLineCompilerConfiguration compilerConfig) { String warn64 = null; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/Wp64".equals(args[i])) { warn64 = trueLiteral; } } return warn64; } /** * Get value of DebugInformationFormat property. * @param compilerConfig compiler configuration. * @return value of DebugInformationFormat property. */ private String getDebugInformationFormat( final CommandLineCompilerConfiguration compilerConfig) { String format = "0"; String[] args = compilerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/Z7".equals(args[i])) { format = "1"; } if ("/Zd".equals(args[i])) { format = "2"; } if ("/Zi".equals(args[i])) { format = "3"; } if ("/ZI".equals(args[i])) { format = "4"; } } return format; } /** * write the Compiler element. * @param content serialization content handler. * @param isDebug true if generating debug configuration. * @param basePath base for relative file paths. * @param compilerConfig compiler configuration. * @throws SAXException thrown if error during serialization. */ private void writeCompilerElement(final ContentHandler content, final boolean isDebug, final String basePath, final CommandLineCompilerConfiguration compilerConfig) throws SAXException { AttributesImpl attributes = new AttributesImpl(); addAttribute(attributes, "Name", "VCCLCompilerTool"); String optimization = getOptimization(compilerConfig); String debugFormat = getDebugInformationFormat(compilerConfig); if(isDebug) { optimization = "0"; if ("0".equals(debugFormat)) { debugFormat = "4"; } } else { if ("0".equals(optimization)) { optimization = "2"; } debugFormat = "0"; } addAttribute(attributes, "Optimization", optimization); addAttribute(attributes, "AdditionalIncludeDirectories", getAdditionalIncludeDirectories(basePath, compilerConfig)); addAttribute(attributes, "PreprocessorDefinitions", getPreprocessorDefinitions(compilerConfig, isDebug)); addAttribute(attributes, "MinimalRebuild", trueLiteral); addAttribute(attributes, "BasicRuntimeChecks", getBasicRuntimeChecks(compilerConfig)); addAttribute(attributes, "RuntimeLibrary", getRuntimeLibrary(compilerConfig, isDebug)); addAttribute(attributes, "UsePrecompiledHeader", getUsePrecompiledHeader(compilerConfig)); addAttribute(attributes, "PrecompiledHeaderFile", getPrecompiledHeaderFile(compilerConfig)); addAttribute(attributes, "WarningLevel", getWarningLevel(compilerConfig)); addAttribute(attributes, "Detect64BitPortabilityProblems", getDetect64BitPortabilityProblems(compilerConfig)); addAttribute(attributes, "DebugInformationFormat", debugFormat); content.startElement(null, "Tool", "Tool", attributes); content.endElement(null, "Tool", "Tool"); } /** * Get value of LinkIncremental property. * @param linkerConfig linker configuration. * @return value of LinkIncremental property */ private String getLinkIncremental( final CommandLineLinkerConfiguration linkerConfig) { String incremental = "0"; String[] args = linkerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/INCREMENTAL:NO".equals(args[i])) { incremental = "1"; } if ("/INCREMENTAL:YES".equals(args[i])) { incremental = "2"; } } return incremental; } /** * Get value of Subsystem property. * @param linkerConfig linker configuration. * @return value of Subsystem property */ private String getSubsystem( final CommandLineLinkerConfiguration linkerConfig) { String subsystem = "0"; String[] args = linkerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/SUBSYSTEM:CONSOLE".equals(args[i])) { subsystem = "1"; } if ("/SUBSYSTEM:WINDOWS".equals(args[i])) { subsystem = "2"; } if ("/SUBSYSTEM:WINDOWSCE".equals(args[i])) { subsystem = "9"; } } return subsystem; } /** * Get value of TargetMachine property. * @param linkerConfig linker configuration. * @return value of TargetMachine property */ private String getTargetMachine( final CommandLineLinkerConfiguration linkerConfig) { String subsystem = "0"; String[] args = linkerConfig.getPreArguments(); for (int i = 0; i < args.length; i++) { if ("/MACHINE:X86".equals(args[i])) { subsystem = "1"; } } return subsystem; } /** * Get value of AdditionalDependencies property. * @param linkTarget link target. * @param projectDependencies dependencies declared in project. * @param targets all targets. * @param basePath path to directory containing project file. * @return value of AdditionalDependencies property. */ private String getAdditionalDependencies(final TargetInfo linkTarget, final List projectDependencies, final Map targets, final String basePath) { String dependencies = null; File[] linkSources = linkTarget.getAllSources(); StringBuffer buf = new StringBuffer(); for (int i = 0; i < linkSources.length; i++) { // // if file was not compiled or otherwise generated // if (targets.get(linkSources[i].getName()) == null) { // // if source appears to be a system library or object file // just output the name of the file (advapi.lib for example) // otherwise construct a relative path. // String relPath = linkSources[i].getName(); // // check if file comes from a project dependency // if it does it should not be explicitly linked boolean fromDependency = false; if (relPath.indexOf(".") > 0) { String baseName = relPath.substring(0, relPath.indexOf(".")); for(Iterator iter = projectDependencies.iterator(); iter.hasNext(); ) { DependencyDef depend = (DependencyDef) iter.next(); if (baseName.compareToIgnoreCase(depend.getName()) == 0) { fromDependency = true; } } } if (!fromDependency) { if (!CUtil.isSystemPath(linkSources[i])) { relPath = CUtil.getRelativePath(basePath, linkSources[i]); } // // if path has an embedded space then // must quote if (relPath.indexOf(' ') > 0) { buf.append('\"'); buf.append(CUtil.toWindowsPath(relPath)); buf.append('\"'); } else { buf.append(relPath); } buf.append(' '); } } } if (buf.length() > 0) { buf.setLength(buf.length() - 1); dependencies = buf.toString(); } return dependencies; } /** * Write Tool element for linker. * @param content serialization content handler. * @param isDebug true if generating debug configuration. * @param dependencies project dependencies. * @param basePath path to directory containing project file. * @param linkTarget link target. * @param targets all targets. * @throws SAXException thrown if error during serialization. */ private void writeLinkerElement(final ContentHandler content, final boolean isDebug, final List dependencies, final String basePath, final TargetInfo linkTarget, final Map targets) throws SAXException { AttributesImpl attributes = new AttributesImpl(); addAttribute(attributes, "Name", "VCLinkerTool"); ProcessorConfiguration config = linkTarget.getConfiguration(); if (config instanceof CommandLineLinkerConfiguration) { CommandLineLinkerConfiguration linkerConfig = (CommandLineLinkerConfiguration) config; if (linkerConfig.getLinker() instanceof DevStudioCompatibleLinker) { addAttribute(attributes, "LinkIncremental", getLinkIncremental(linkerConfig)); if (isDebug) { addAttribute(attributes, "GenerateDebugInformation", trueLiteral); } else { addAttribute(attributes, "GenerateDebugInformation", falseLiteral); } addAttribute(attributes, "SubSystem", getSubsystem(linkerConfig)); addAttribute(attributes, "TargetMachine", getTargetMachine(linkerConfig)); } } addAttribute(attributes, "AdditionalDependencies", getAdditionalDependencies(linkTarget, dependencies, targets, basePath)); content.startElement(null, "Tool", "Tool", attributes); content.endElement(null, "Tool", "Tool"); } /** * Writes a project definition file. * * @param fileName project name for file, should has .cbx extension * @param task cc task for which to write project * @param projectDef project element * @param sources source files * @param targets compilation targets * @param linkTarget link target * @throws IOException if I/O error * @throws SAXException if XML serialization error */ public void writeProject(final File fileName, final CCTask task, final ProjectDef projectDef, final List sources, final Map targets, final TargetInfo linkTarget) throws IOException, SAXException { String projectName = projectDef.getName(); if (projectName == null) { projectName = fileName.getName(); } File vcprojFile = new File(fileName + ".vcproj"); if (!projectDef.getOverwrite() && vcprojFile.exists()) { throw new BuildException("Not allowed to overwrite project file " + vcprojFile.toString()); } File slnFile = new File(fileName + ".sln"); if (!projectDef.getOverwrite() && slnFile.exists()) { throw new BuildException("Not allowed to overwrite project file " + slnFile.toString()); } CommandLineCompilerConfiguration compilerConfig = getBaseCompilerConfiguration(targets); if (compilerConfig == null) { throw new BuildException( "Unable to generate Visual Studio.NET project " + "when Microsoft C++ is not used."); } OutputStream outStream = new FileOutputStream(fileName + ".vcproj"); OutputFormat format = new OutputFormat("xml", "UTF-8", true); XMLSerializer serializer = new XMLSerializer(outStream, format); ContentHandler content = serializer.asContentHandler(); String basePath = fileName.getParentFile().getAbsolutePath(); content.startDocument(); for(Iterator iter = projectDef.getComments().iterator(); iter.hasNext(); ) { String comment = ((CommentDef) iter.next()).getText(); serializer.comment(comment); } AttributesImpl emptyAttrs = new AttributesImpl(); AttributesImpl attributes = new AttributesImpl(); addAttribute(attributes, "ProjectType", "Visual C++"); addAttribute(attributes, "Version", version); addAttribute(attributes, "Name", projectName); content.startElement(null, "VisualStudioProject", "VisualStudioProject", attributes); content.startElement(null, "Platforms", "Platforms", emptyAttrs); attributes.clear(); addAttribute(attributes, "Name", "Win32"); content.startElement(null, "Platform", "Platform", attributes); content.endElement(null, "Platform", "Platform"); content.endElement(null, "Platforms", "Platforms"); content.startElement(null, "Configurations", "Configurations", emptyAttrs); // // write debug configuration // writeConfigurationStartTag(content, true, task, compilerConfig); writeCompilerElement(content, true, basePath, compilerConfig); writeLinkerElement(content, true, projectDef.getDependencies(), basePath, linkTarget, targets); content.endElement(null, "Configuration", "Configuration"); // // write release configuration // writeConfigurationStartTag(content, false, task, compilerConfig); writeCompilerElement(content, false, basePath, compilerConfig); writeLinkerElement(content, false, projectDef.getDependencies(), basePath, linkTarget, targets); content.endElement(null, "Configuration", "Configuration"); content.endElement(null, "Configurations", "Configurations"); content.startElement(null, "References", "References", emptyAttrs); content.endElement(null, "References", "References"); content.startElement(null, "Files", "Files", emptyAttrs); File[] sortedSources = new File[sources.size()]; sources.toArray(sortedSources); Arrays.sort(sortedSources, new Comparator() { public int compare(final Object o1, final Object o2) { return ((File) o1).getName().compareTo(((File) o2).getName()); } }); writeFilteredSources("Source Files", "cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx", basePath, sortedSources, content); writeFilteredSources("Header Files", "h;hpp;hxx;hm;inl;inc;xsd", basePath, sortedSources, content); writeFilteredSources("Resource Files", "rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx", basePath, sortedSources, content); content.endElement(null, "Files", "Files"); content.startElement(null, "Globals", "Globals", emptyAttrs); content.endElement(null, "Globals", "Globals"); content.endElement(null, "VisualStudioProject", "VisualStudioProject"); content.endDocument(); } /** * Writes a cluster of source files to the project. * * @param name name of filter * @param filter file extensions * @param basePath base path for files * @param sortedSources array of source files * @param content generated project * @throws SAXException if invalid content */ private void writeFilteredSources(final String name, final String filter, final String basePath, final File[] sortedSources, final ContentHandler content) throws SAXException { AttributesImpl filterAttrs = new AttributesImpl(); filterAttrs.addAttribute(null, "Name", "Name", "#PCDATA", name); filterAttrs.addAttribute(null, "Filter", "Filter", "#PCDATA", filter); content.startElement(null, "Filter", "Filter", filterAttrs); AttributesImpl fileAttrs = new AttributesImpl(); fileAttrs.addAttribute(null, "RelativePath", "RelativePath", "#PCDATA", ""); for (int i = 0; i < sortedSources.length; i++) { if (isGroupMember(filter, sortedSources[i])) { String relativePath = CUtil.getRelativePath(basePath, sortedSources[i]); fileAttrs.setValue(0, relativePath); content.startElement(null, "File", "File", fileAttrs); content.endElement(null, "File", "File"); } } content.endElement(null, "Filter", "Filter"); } /** * Returns true if the file has an extension that * appears in the group filter. * * @param filter String group filter * @param candidate File file * @return boolean true if member of group */ private boolean isGroupMember(final String filter, final File candidate) { String fileName = candidate.getName(); int lastDot = fileName.lastIndexOf('.'); if (lastDot >= 0 && lastDot < fileName.length() - 1) { String extension = ";" + fileName.substring(lastDot + 1).toLowerCase() + ";"; String semiFilter = ";" + filter + ";"; return semiFilter.indexOf(extension) >= 0; } return false; } /** * Adds an non-namespace-qualified attribute to attribute list. * @param attributes list of attributes. * @param attrName attribute name, may not be null. * @param attrValue attribute value, if null attribute is not added. */ private static void addAttribute(final AttributesImpl attributes, final String attrName, final String attrValue) { if (attrName == null) { throw new IllegalArgumentException("attrName"); } if (attrValue != null) { attributes.addAttribute(null, attrName, attrName, "#PCDATA", attrValue); } } /** * Gets the first recognized compiler from the * compilation targets. * @param targets compilation targets * @return representative (hopefully) compiler configuration */ private CommandLineCompilerConfiguration getBaseCompilerConfiguration(final Map targets) { // // get the first target and assume that it is representative // Iterator targetIter = targets.values().iterator(); while (targetIter.hasNext()) { TargetInfo targetInfo = (TargetInfo) targetIter.next(); ProcessorConfiguration config = targetInfo.getConfiguration(); // // for the first cl compiler // if (config instanceof CommandLineCompilerConfiguration) { CommandLineCompilerConfiguration compilerConfig = (CommandLineCompilerConfiguration) config; if (compilerConfig.getCompiler() instanceof DevStudioCCompiler) { return compilerConfig; } } } return null; } }