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.msvc;
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.util.Arrays;
27  import java.util.Comparator;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.tools.ant.BuildException;
32  import org.apache.xml.serialize.OutputFormat;
33  import org.apache.xml.serialize.XMLSerializer;
34  import org.xml.sax.ContentHandler;
35  import org.xml.sax.SAXException;
36  import org.xml.sax.helpers.AttributesImpl;
37  
38  import com.github.maven_nar.cpptasks.CCTask;
39  import com.github.maven_nar.cpptasks.CUtil;
40  import com.github.maven_nar.cpptasks.TargetInfo;
41  import com.github.maven_nar.cpptasks.compiler.CommandLineCompilerConfiguration;
42  import com.github.maven_nar.cpptasks.compiler.CommandLineLinkerConfiguration;
43  import com.github.maven_nar.cpptasks.compiler.ProcessorConfiguration;
44  import com.github.maven_nar.cpptasks.ide.CommentDef;
45  import com.github.maven_nar.cpptasks.ide.DependencyDef;
46  import com.github.maven_nar.cpptasks.ide.ProjectDef;
47  import com.github.maven_nar.cpptasks.ide.ProjectWriter;
48  
49  /**
50   * Writes a Visual Studio.NET project file.
51   *
52   * @author curta
53   */
54  public final class VisualStudioNETProjectWriter implements ProjectWriter {
55    /**
56     * Adds an non-namespace-qualified attribute to attribute list.
57     * 
58     * @param attributes
59     *          list of attributes.
60     * @param attrName
61     *          attribute name, may not be null.
62     * @param attrValue
63     *          attribute value, if null attribute is not added.
64     */
65    private static void addAttribute(final AttributesImpl attributes, final String attrName, final String attrValue) {
66      if (attrName == null) {
67        throw new IllegalArgumentException("attrName");
68      }
69      if (attrValue != null) {
70        attributes.addAttribute(null, attrName, attrName, "#PCDATA", attrValue);
71      }
72    }
73  
74    /**
75     * Version of VisualStudio.NET.
76     */
77    private final String version;
78  
79    /**
80     * Literal to represent a true value.
81     */
82    private final String trueLiteral;
83  
84    /**
85     * Literal to represent a false value.
86     */
87    private final String falseLiteral;
88  
89    /**
90     * Constructor.
91     *
92     * @param versionArg
93     *          String VisualStudio.NET version
94     * @param trueArg
95     *          literal to represent true, "true" in VC 2005.
96     * @param falseArg
97     *          literal to represent false, "false" in VC 2005.
98     */
99    public VisualStudioNETProjectWriter(final String versionArg, final String trueArg, final String falseArg) {
100     if (versionArg == null) {
101       throw new IllegalArgumentException("versionArg");
102     }
103     if (trueArg == null) {
104       throw new IllegalArgumentException("trueArg");
105     }
106     if (falseArg == null) {
107       throw new IllegalArgumentException("falseArg");
108     }
109     this.version = versionArg;
110     this.trueLiteral = trueArg;
111     this.falseLiteral = falseArg;
112   }
113 
114   /**
115    * Get value of AdditionalDependencies property.
116    * 
117    * @param linkTarget
118    *          link target.
119    * @param projectDependencies
120    *          dependencies declared in project.
121    * @param targets
122    *          all targets.
123    * @param basePath
124    *          path to directory containing project file.
125    * @return value of AdditionalDependencies property.
126    */
127   private String getAdditionalDependencies(final TargetInfo linkTarget, final List<DependencyDef> projectDependencies,
128       final Map<String, TargetInfo> targets, final String basePath) {
129     String dependencies = null;
130     final File[] linkSources = linkTarget.getAllSources();
131     final StringBuffer buf = new StringBuffer();
132     for (final File linkSource : linkSources) {
133       //
134       // if file was not compiled or otherwise generated
135       //
136       if (targets.get(linkSource.getName()) == null) {
137         //
138         // if source appears to be a system library or object file
139         // just output the name of the file (advapi.lib for example)
140         // otherwise construct a relative path.
141         //
142         String relPath = linkSource.getName();
143         //
144         // check if file comes from a project dependency
145         // if it does it should not be explicitly linked
146         boolean fromDependency = false;
147         if (relPath.indexOf(".") > 0) {
148           final String baseName = relPath.substring(0, relPath.indexOf("."));
149           for (DependencyDef depend : projectDependencies) {
150             if (baseName.compareToIgnoreCase(depend.getName()) == 0) {
151               fromDependency = true;
152             }
153           }
154         }
155 
156         if (!fromDependency) {
157           if (!CUtil.isSystemPath(linkSource)) {
158             relPath = CUtil.getRelativePath(basePath, linkSource);
159           }
160           //
161           // if path has an embedded space then
162           // must quote
163           if (relPath.indexOf(' ') > 0) {
164             buf.append('\"');
165             buf.append(CUtil.toWindowsPath(relPath));
166             buf.append('\"');
167           } else {
168             buf.append(relPath);
169           }
170           buf.append(' ');
171         }
172       }
173     }
174     if (buf.length() > 0) {
175       buf.setLength(buf.length() - 1);
176       dependencies = buf.toString();
177     }
178     return dependencies;
179 
180   }
181 
182   /**
183    * Get value of AdditionalIncludeDirectories property.
184    * 
185    * @param compilerConfig
186    *          compiler configuration.
187    * @param baseDir
188    *          base for relative paths.
189    * @return value of AdditionalIncludeDirectories property.
190    */
191   private String getAdditionalIncludeDirectories(final String baseDir,
192       final CommandLineCompilerConfiguration compilerConfig) {
193     final File[] includePath = compilerConfig.getIncludePath();
194     final StringBuffer includeDirs = new StringBuffer();
195     // Darren Sargent Feb 10 2010 -- reverted to older code to ensure sys
196     // includes get, erm, included
197     final String[] args = compilerConfig.getPreArguments();
198 
199     for (final String arg : args) {
200       if (arg.startsWith("/I")) {
201         includeDirs.append(arg.substring(2));
202         includeDirs.append(';');
203       }
204 
205     }
206     // end Darren
207 
208     if (includeDirs.length() > 0) {
209       includeDirs.setLength(includeDirs.length() - 1);
210     }
211     return includeDirs.toString();
212   }
213 
214   /**
215    * Gets the first recognized compiler from the
216    * compilation targets.
217    * 
218    * @param targets
219    *          compilation targets
220    * @return representative (hopefully) compiler configuration
221    */
222   private CommandLineCompilerConfiguration getBaseCompilerConfiguration(final Map<String, TargetInfo> targets) {
223     //
224     // get the first target and assume that it is representative
225     //
226     for (final TargetInfo targetInfo : targets.values()) {
227       final ProcessorConfiguration config = targetInfo.getConfiguration();
228       //
229       // for the first cl compiler
230       //
231       if (config instanceof CommandLineCompilerConfiguration) {
232         final CommandLineCompilerConfiguration compilerConfig = (CommandLineCompilerConfiguration) config;
233         if (compilerConfig.getCompiler() instanceof MsvcCCompiler) {
234           return compilerConfig;
235         }
236       }
237     }
238     return null;
239   }
240 
241   /**
242    * Get value of BasicRuntimeChecks property.
243    * 
244    * @param compilerConfig
245    *          compiler configuration.
246    * @return value of BasicRuntimeChecks property.
247    */
248   private String getBasicRuntimeChecks(final CommandLineCompilerConfiguration compilerConfig) {
249     String checks = "0";
250     final String[] args = compilerConfig.getPreArguments();
251     for (final String arg : args) {
252       if ("/RTCs".equals(arg)) {
253         checks = "1";
254       }
255       if ("/RTCu".equals(arg)) {
256         checks = "2";
257       }
258       if ("/RTC1".equals(arg) || "/GZ".equals(arg)) {
259         checks = "3";
260       }
261     }
262     return checks;
263   }
264 
265   /**
266    * Get character set for Windows API.
267    * 
268    * @param compilerConfig
269    *          compiler configuration, may not be null.
270    * @return "1" is TCHAR is unicode, "0" if TCHAR is multi-byte.
271    */
272   private String getCharacterSet(final CommandLineCompilerConfiguration compilerConfig) {
273     final String[] args = compilerConfig.getPreArguments();
274     String charset = "0";
275     for (final String arg : args) {
276       if ("/D_UNICODE".equals(arg) || "/DUNICODE".equals(arg)) {
277         charset = "1";
278       }
279       if ("/D_MBCS".equals(arg)) {
280         charset = "2";
281       }
282     }
283     return charset;
284   }
285 
286   /**
287    * Gets the configuration type.
288    *
289    * @param task
290    *          cc task, may not be null.
291    * @return configuration type
292    */
293   private String getConfigurationType(final CCTask task) {
294     final String outputType = task.getOuttype();
295     String targtype = "2"; // Win32 (x86) Dynamic-Link Library";
296     if ("executable".equals(outputType)) {
297       targtype = "1"; // "Win32 (x86) Console Application";
298     } else if ("static".equals(outputType)) {
299       targtype = "4"; // "Win32 (x86) Static Library";
300     }
301     return targtype;
302   }
303 
304   /**
305    * Get value of DebugInformationFormat property.
306    * 
307    * @param compilerConfig
308    *          compiler configuration.
309    * @return value of DebugInformationFormat property.
310    */
311   private String getDebugInformationFormat(final CommandLineCompilerConfiguration compilerConfig) {
312     String format = "0";
313     final String[] args = compilerConfig.getPreArguments();
314     for (final String arg : args) {
315       if ("/Z7".equals(arg)) {
316         format = "1";
317       }
318       if ("/Zd".equals(arg)) {
319         format = "2";
320       }
321       if ("/Zi".equals(arg)) {
322         format = "3";
323       }
324       if ("/ZI".equals(arg)) {
325         format = "4";
326       }
327     }
328     return format;
329   }
330 
331   /**
332    * Get value of Detect64BitPortabilityProblems property.
333    * 
334    * @param compilerConfig
335    *          compiler configuration.
336    * @return value of Detect64BitPortabilityProblems property.
337    */
338   private String getDetect64BitPortabilityProblems(final CommandLineCompilerConfiguration compilerConfig) {
339     String warn64 = null;
340     final String[] args = compilerConfig.getPreArguments();
341     for (final String arg : args) {
342       if ("/Wp64".equals(arg)) {
343         warn64 = this.trueLiteral;
344       }
345     }
346     return warn64;
347   }
348 
349   /**
350    * Get value of LinkIncremental property.
351    * 
352    * @param linkerConfig
353    *          linker configuration.
354    * @return value of LinkIncremental property
355    */
356   private String getLinkIncremental(final CommandLineLinkerConfiguration linkerConfig) {
357     String incremental = "0";
358     final String[] args = linkerConfig.getPreArguments();
359     for (final String arg : args) {
360       if ("/INCREMENTAL:NO".equals(arg)) {
361         incremental = "1";
362       }
363       if ("/INCREMENTAL:YES".equals(arg)) {
364         incremental = "2";
365       }
366     }
367     return incremental;
368   }
369 
370   /**
371    * Get value of Optimization property.
372    * 
373    * @param compilerConfig
374    *          compiler configuration, may not be null.
375    * @return value of Optimization property.
376    */
377   private String getOptimization(final CommandLineCompilerConfiguration compilerConfig) {
378     final String[] args = compilerConfig.getPreArguments();
379     String opt = "0";
380     for (final String arg : args) {
381       if ("/Od".equals(arg)) {
382         opt = "0";
383       }
384       if ("/O1".equals(arg)) {
385         opt = "1";
386       }
387       if ("/O2".equals(arg)) {
388         opt = "2";
389       }
390       if ("/Ox".equals(arg)) {
391         opt = "3";
392       }
393     }
394     return opt;
395   }
396 
397   /**
398    * Get value of PrecompiledHeaderFile property.
399    * 
400    * @param compilerConfig
401    *          compiler configuration.
402    * @return value of PrecompiledHeaderFile property.
403    */
404   private String getPrecompiledHeaderFile(final CommandLineCompilerConfiguration compilerConfig) {
405     String pch = null;
406     final String[] args = compilerConfig.getPreArguments();
407     for (final String arg : args) {
408       if (arg.startsWith("/Fp")) {
409         pch = arg.substring(3);
410       }
411     }
412     return pch;
413   }
414 
415   /**
416    * Get value of PreprocessorDefinitions property.
417    * 
418    * @param compilerConfig
419    *          compiler configuration.
420    * @param isDebug
421    *          true if generating debug configuration.
422    * @return value of PreprocessorDefinitions property.
423    */
424   private String
425       getPreprocessorDefinitions(final CommandLineCompilerConfiguration compilerConfig, final boolean isDebug) {
426     final StringBuffer defines = new StringBuffer();
427     final String[] args = compilerConfig.getPreArguments();
428     for (final String arg : args) {
429       if (arg.startsWith("/D")) {
430         String macro = arg.substring(2);
431         if (isDebug) {
432           if (macro.equals("NDEBUG")) {
433             macro = "_DEBUG";
434           }
435         } else {
436           if (macro.equals("_DEBUG")) {
437             macro = "NDEBUG";
438           }
439         }
440         defines.append(macro);
441         defines.append(";");
442       }
443     }
444 
445     if (defines.length() > 0) {
446       defines.setLength(defines.length() - 1);
447     }
448     return defines.toString();
449   }
450 
451   /**
452    * Get value of RuntimeLibrary property.
453    * 
454    * @param compilerConfig
455    *          compiler configuration.
456    * @param isDebug
457    *          true if generating debug configuration.
458    * @return value of RuntimeLibrary property.
459    */
460   private String getRuntimeLibrary(final CommandLineCompilerConfiguration compilerConfig, final boolean isDebug) {
461     String rtl = null;
462     final String[] args = compilerConfig.getPreArguments();
463     for (final String arg : args) {
464       if (arg.startsWith("/MT")) {
465         if (isDebug) {
466           rtl = "1";
467         } else {
468           rtl = "0";
469         }
470       } else if (arg.startsWith("/MD")) {
471         if (isDebug) {
472           rtl = "3";
473         } else {
474           rtl = "2";
475         }
476       }
477     }
478     return rtl;
479   }
480 
481   /**
482    * Get value of Subsystem property.
483    * 
484    * @param linkerConfig
485    *          linker configuration.
486    * @return value of Subsystem property
487    */
488   private String getSubsystem(final CommandLineLinkerConfiguration linkerConfig) {
489     String subsystem = "0";
490     final String[] args = linkerConfig.getPreArguments();
491     for (final String arg : args) {
492       if ("/SUBSYSTEM:CONSOLE".equals(arg)) {
493         subsystem = "1";
494       }
495       if ("/SUBSYSTEM:WINDOWS".equals(arg)) {
496         subsystem = "2";
497       }
498       if ("/SUBSYSTEM:WINDOWSCE".equals(arg)) {
499         subsystem = "9";
500       }
501     }
502     return subsystem;
503   }
504 
505   /**
506    * Get value of TargetMachine property.
507    * 
508    * @param linkerConfig
509    *          linker configuration.
510    * @return value of TargetMachine property
511    */
512   private String getTargetMachine(final CommandLineLinkerConfiguration linkerConfig) {
513     String subsystem = "0";
514     final String[] args = linkerConfig.getPreArguments();
515     for (final String arg : args) {
516       if ("/MACHINE:X86".equals(arg)) {
517         subsystem = "1";
518       }
519     }
520     return subsystem;
521   }
522 
523   /**
524    * Get value of UsePrecompiledHeader property.
525    * 
526    * @param compilerConfig
527    *          compiler configuration.
528    * @return value of UsePrecompiledHeader property.
529    */
530   private String getUsePrecompiledHeader(final CommandLineCompilerConfiguration compilerConfig) {
531     String usePCH = "0";
532     final String[] args = compilerConfig.getPreArguments();
533     for (final String arg : args) {
534       if ("/Yc".equals(arg)) {
535         usePCH = "1";
536       }
537       if ("/Yu".equals(arg)) {
538         usePCH = "2";
539       }
540     }
541     return usePCH;
542   }
543 
544   /**
545    * Get value of WarningLevel property.
546    * 
547    * @param compilerConfig
548    *          compiler configuration.
549    * @return value of WarningLevel property.
550    */
551   private String getWarningLevel(final CommandLineCompilerConfiguration compilerConfig) {
552     String warn = null;
553     final String[] args = compilerConfig.getPreArguments();
554     for (final String arg : args) {
555       if ("/W0".equals(arg)) {
556         warn = "0";
557       }
558       if ("/W1".equals(arg)) {
559         warn = "1";
560       }
561       if ("/W2".equals(arg)) {
562         warn = "2";
563       }
564       if ("/W3".equals(arg)) {
565         warn = "3";
566       }
567       // Added by Darren Sargent, 2/26/2008
568       if ("/W4".equals(arg)) {
569         warn = "4";
570       }
571       // end added
572     }
573     return warn;
574   }
575 
576   /**
577    * Returns true if the file has an extension that
578    * appears in the group filter.
579    *
580    * @param filter
581    *          String group filter
582    * @param candidate
583    *          File file
584    * @return boolean true if member of group
585    */
586   private boolean isGroupMember(final String filter, final File candidate) {
587     final String fileName = candidate.getName();
588     final int lastDot = fileName.lastIndexOf('.');
589     if (lastDot >= 0 && lastDot < fileName.length() - 1) {
590       final String extension = ";" + fileName.substring(lastDot + 1).toLowerCase() + ";";
591       final String semiFilter = ";" + filter + ";";
592       return semiFilter.contains(extension);
593     }
594     return false;
595   }
596 
597   /**
598    * write the Compiler element.
599    * 
600    * @param content
601    *          serialization content handler.
602    * @param isDebug
603    *          true if generating debug configuration.
604    * @param basePath
605    *          base for relative file paths.
606    * @param compilerConfig
607    *          compiler configuration.
608    * @throws SAXException
609    *           thrown if error during serialization.
610    */
611   private void writeCompilerElement(final ContentHandler content, final boolean isDebug, final String basePath,
612       final CommandLineCompilerConfiguration compilerConfig) throws SAXException {
613     final AttributesImpl attributes = new AttributesImpl();
614     addAttribute(attributes, "Name", "VCCLCompilerTool");
615     String optimization = getOptimization(compilerConfig);
616     String debugFormat = getDebugInformationFormat(compilerConfig);
617     if (isDebug) {
618       optimization = "0";
619       if ("0".equals(debugFormat)) {
620         debugFormat = "4";
621       }
622     } else {
623       if ("0".equals(optimization)) {
624         optimization = "2";
625       }
626       debugFormat = "0";
627     }
628     addAttribute(attributes, "Optimization", optimization);
629     addAttribute(attributes, "AdditionalIncludeDirectories", getAdditionalIncludeDirectories(basePath, compilerConfig));
630     addAttribute(attributes, "PreprocessorDefinitions", getPreprocessorDefinitions(compilerConfig, isDebug));
631     addAttribute(attributes, "MinimalRebuild", this.trueLiteral);
632     addAttribute(attributes, "BasicRuntimeChecks", getBasicRuntimeChecks(compilerConfig));
633     addAttribute(attributes, "RuntimeLibrary", getRuntimeLibrary(compilerConfig, isDebug));
634     addAttribute(attributes, "UsePrecompiledHeader", getUsePrecompiledHeader(compilerConfig));
635     addAttribute(attributes, "PrecompiledHeaderFile", getPrecompiledHeaderFile(compilerConfig));
636     addAttribute(attributes, "WarningLevel", getWarningLevel(compilerConfig));
637     addAttribute(attributes, "Detect64BitPortabilityProblems", getDetect64BitPortabilityProblems(compilerConfig));
638     addAttribute(attributes, "DebugInformationFormat", debugFormat);
639     content.startElement(null, "Tool", "Tool", attributes);
640     content.endElement(null, "Tool", "Tool");
641 
642   }
643 
644   /**
645    * Write the start tag of the Configuration element.
646    * 
647    * @param content
648    *          serialization content handler.
649    * @param isDebug
650    *          if true, write a debug configuration.
651    * @param task
652    *          cc task.
653    * @param compilerConfig
654    *          compiler configuration.
655    * @throws SAXException
656    *           thrown if serialization error.
657    */
658   private void writeConfigurationStartTag(final ContentHandler content, final boolean isDebug, final CCTask task,
659       final CommandLineCompilerConfiguration compilerConfig) throws SAXException {
660     final AttributesImpl attributes = new AttributesImpl();
661     if (isDebug) {
662       addAttribute(attributes, "Name", "Debug|Win32");
663       addAttribute(attributes, "OutputDirectory", "Debug");
664       addAttribute(attributes, "IntermediateDirectory", "Debug");
665     } else {
666       addAttribute(attributes, "Name", "Release|Win32");
667       addAttribute(attributes, "OutputDirectory", "Release");
668       addAttribute(attributes, "IntermediateDirectory", "Release");
669 
670     }
671     addAttribute(attributes, "ConfigurationType", getConfigurationType(task));
672     addAttribute(attributes, "CharacterSet", getCharacterSet(compilerConfig));
673     content.startElement(null, "Configuration", "Configuration", attributes);
674   }
675 
676   /**
677    * Writes a cluster of source files to the project.
678    *
679    * @param name
680    *          name of filter
681    * @param filter
682    *          file extensions
683    * @param basePath
684    *          base path for files
685    * @param sortedSources
686    *          array of source files
687    * @param content
688    *          generated project
689    * @throws SAXException
690    *           if invalid content
691    */
692   private void writeFilteredSources(final String name, final String filter, final String basePath,
693       final File[] sortedSources, final ContentHandler content) throws SAXException {
694     final AttributesImpl filterAttrs = new AttributesImpl();
695     filterAttrs.addAttribute(null, "Name", "Name", "#PCDATA", name);
696     filterAttrs.addAttribute(null, "Filter", "Filter", "#PCDATA", filter);
697     content.startElement(null, "Filter", "Filter", filterAttrs);
698 
699     final AttributesImpl fileAttrs = new AttributesImpl();
700     fileAttrs.addAttribute(null, "RelativePath", "RelativePath", "#PCDATA", "");
701 
702     for (final File sortedSource : sortedSources) {
703       if (isGroupMember(filter, sortedSource)) {
704         final String relativePath = CUtil.getRelativePath(basePath, sortedSource);
705         fileAttrs.setValue(0, relativePath);
706         content.startElement(null, "File", "File", fileAttrs);
707         content.endElement(null, "File", "File");
708       }
709     }
710     content.endElement(null, "Filter", "Filter");
711 
712   }
713 
714   /**
715    * Write Tool element for linker.
716    * 
717    * @param content
718    *          serialization content handler.
719    * @param isDebug
720    *          true if generating debug configuration.
721    * @param dependencies
722    *          project dependencies.
723    * @param basePath
724    *          path to directory containing project file.
725    * @param linkTarget
726    *          link target.
727    * @param targets
728    *          all targets.
729    * @throws SAXException
730    *           thrown if error during serialization.
731    */
732   private void writeLinkerElement(final ContentHandler content, final boolean isDebug,
733       final List<DependencyDef> dependencies, final String basePath, final TargetInfo linkTarget,
734       final Map<String, TargetInfo> targets) throws SAXException {
735     final AttributesImpl attributes = new AttributesImpl();
736     addAttribute(attributes, "Name", "VCLinkerTool");
737 
738     final ProcessorConfiguration config = linkTarget.getConfiguration();
739     if (config instanceof CommandLineLinkerConfiguration) {
740       final CommandLineLinkerConfiguration linkerConfig = (CommandLineLinkerConfiguration) config;
741       if (linkerConfig.getLinker() instanceof MsvcCompatibleLinker) {
742         addAttribute(attributes, "LinkIncremental", getLinkIncremental(linkerConfig));
743         if (isDebug) {
744           addAttribute(attributes, "GenerateDebugInformation", this.trueLiteral);
745         } else {
746           addAttribute(attributes, "GenerateDebugInformation", this.falseLiteral);
747         }
748         addAttribute(attributes, "SubSystem", getSubsystem(linkerConfig));
749         addAttribute(attributes, "TargetMachine", getTargetMachine(linkerConfig));
750       }
751     }
752     addAttribute(attributes, "AdditionalDependencies",
753         getAdditionalDependencies(linkTarget, dependencies, targets, basePath));
754     content.startElement(null, "Tool", "Tool", attributes);
755     content.endElement(null, "Tool", "Tool");
756   }
757 
758   /**
759    * Writes a project definition file.
760    *
761    * @param fileName
762    *          project name for file, should has .cbx extension
763    * @param task
764    *          cc task for which to write project
765    * @param projectDef
766    *          project element
767    * @param sources
768    *          source files
769    * @param targets
770    *          compilation targets
771    * @param linkTarget
772    *          link target
773    * @throws IOException
774    *           if I/O error
775    * @throws SAXException
776    *           if XML serialization error
777    */
778   @Override
779   public void writeProject(final File fileName, final CCTask task, final ProjectDef projectDef,
780       final List<File> sources, final Map<String, TargetInfo> targets, final TargetInfo linkTarget)
781       throws IOException, SAXException {
782 
783     String projectName = projectDef.getName();
784     if (projectName == null) {
785       projectName = fileName.getName();
786     }
787 
788     final File vcprojFile = new File(fileName + ".vcproj");
789     if (!projectDef.getOverwrite() && vcprojFile.exists()) {
790       throw new BuildException("Not allowed to overwrite project file " + vcprojFile.toString());
791     }
792     final File slnFile = new File(fileName + ".sln");
793     if (!projectDef.getOverwrite() && slnFile.exists()) {
794       throw new BuildException("Not allowed to overwrite project file " + slnFile.toString());
795     }
796 
797     final CommandLineCompilerConfiguration compilerConfig = getBaseCompilerConfiguration(targets);
798     if (compilerConfig == null) {
799       throw new BuildException("Unable to generate Visual Studio.NET project " + "when Microsoft C++ is not used.");
800     }
801 
802     final OutputStream outStream = new FileOutputStream(fileName + ".vcproj");
803     final OutputFormat format = new OutputFormat("xml", "UTF-8", true);
804     final XMLSerializer serializer = new XMLSerializer(outStream, format);
805     final ContentHandler content = serializer.asContentHandler();
806     final String basePath = fileName.getParentFile().getAbsolutePath();
807     content.startDocument();
808 
809     for (final CommentDef commentDef : projectDef.getComments()) {
810       final String comment = commentDef.getText();
811       serializer.comment(comment);
812     }
813 
814     final AttributesImpl emptyAttrs = new AttributesImpl();
815 
816     final AttributesImpl attributes = new AttributesImpl();
817     addAttribute(attributes, "ProjectType", "Visual C++");
818     addAttribute(attributes, "Version", this.version);
819     addAttribute(attributes, "Name", projectName);
820     content.startElement(null, "VisualStudioProject", "VisualStudioProject", attributes);
821 
822     content.startElement(null, "Platforms", "Platforms", emptyAttrs);
823     attributes.clear();
824     addAttribute(attributes, "Name", "Win32");
825     content.startElement(null, "Platform", "Platform", attributes);
826     content.endElement(null, "Platform", "Platform");
827     content.endElement(null, "Platforms", "Platforms");
828     content.startElement(null, "Configurations", "Configurations", emptyAttrs);
829 
830     //
831     // write debug configuration
832     //
833     writeConfigurationStartTag(content, true, task, compilerConfig);
834     writeCompilerElement(content, true, basePath, compilerConfig);
835     writeLinkerElement(content, true, projectDef.getDependencies(), basePath, linkTarget, targets);
836     content.endElement(null, "Configuration", "Configuration");
837 
838     //
839     // write release configuration
840     //
841     writeConfigurationStartTag(content, false, task, compilerConfig);
842     writeCompilerElement(content, false, basePath, compilerConfig);
843     writeLinkerElement(content, false, projectDef.getDependencies(), basePath, linkTarget, targets);
844     content.endElement(null, "Configuration", "Configuration");
845 
846     content.endElement(null, "Configurations", "Configurations");
847     content.startElement(null, "References", "References", emptyAttrs);
848     content.endElement(null, "References", "References");
849     content.startElement(null, "Files", "Files", emptyAttrs);
850 
851     final File[] sortedSources = new File[sources.size()];
852     sources.toArray(sortedSources);
853     Arrays.sort(sortedSources, new Comparator<File>() {
854       @Override
855       public int compare(final File o1, final File o2) {
856         return o1.getName().compareTo(o2.getName());
857       }
858     });
859 
860     writeFilteredSources("Source Files", "cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx", basePath, sortedSources, content);
861 
862     writeFilteredSources("Header Files", "h;hpp;hxx;hm;inl;inc;xsd", basePath, sortedSources, content);
863 
864     writeFilteredSources("Resource Files", "rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx", basePath,
865         sortedSources, content);
866 
867     content.endElement(null, "Files", "Files");
868     content.startElement(null, "Globals", "Globals", emptyAttrs);
869     content.endElement(null, "Globals", "Globals");
870     content.endElement(null, "VisualStudioProject", "VisualStudioProject");
871     content.endDocument();
872   }
873 }