1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.github.maven_nar.cpptasks.msvc;
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.text.MessageFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Comparator;
31 import java.util.Hashtable;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35
36 import org.apache.tools.ant.BuildException;
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
51
52
53
54
55
56
57 public final class MsvcProjectWriter implements ProjectWriter {
58 private static String toProjectName(final String name) {
59
60
61
62
63 final StringBuffer projectNameBuf = new StringBuffer(name);
64 for (int i = 0; i < projectNameBuf.length(); i++) {
65 final char ch = projectNameBuf.charAt(i);
66 if (!(ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')) {
67 projectNameBuf.setCharAt(i, '_');
68 }
69 }
70 return projectNameBuf.toString();
71
72 }
73
74 private static void writeComments(final Writer writer, final List<CommentDef> comments) throws IOException {
75 for (final CommentDef commentDef : comments) {
76 final String comment = commentDef.getText();
77 if (comment != null) {
78 int start = 0;
79 for (int end = comment.indexOf('\n'); end != -1; end = comment.indexOf('\n', start)) {
80 writer.write("#" + comment.substring(start, end) + "\r\n");
81 start = end + 1;
82 }
83 }
84 }
85 }
86
87 private static void writeWorkspaceProject(final Writer writer, final String projectName, final String projectFile,
88 final List<String> dependsOn) throws IOException {
89 writer.write("############################################");
90 writer.write("###################################\r\n\r\n");
91 String file = projectFile;
92 if (!file.startsWith(".") && !file.startsWith("\\") && !file.startsWith("/")) {
93 file = ".\\" + file;
94 }
95 writer.write("Project: \"" + projectName + "\"=\"" + file + "\" - Package Owner=<4>\r\n\r\n");
96
97 writer.write("Package=<5>\r\n{{{\r\n}}}\r\n\r\n");
98 writer.write("Package=<4>\r\n{{{\r\n");
99 if (dependsOn != null) {
100 for (final String string : dependsOn) {
101 writer.write(" Begin Project Dependency\r\n");
102 writer.write(" Project_Dep_Name " + toProjectName(String.valueOf(string)) + "\r\n");
103 writer.write(" End Project Dependency\r\n");
104 }
105 }
106 writer.write("}}}\r\n\r\n");
107
108 }
109
110
111
112
113 private final String version;
114
115
116
117
118
119
120
121 public MsvcProjectWriter(final String versionArg) {
122 this.version = versionArg;
123 }
124
125
126
127
128
129
130
131
132
133 private CommandLineCompilerConfiguration getBaseCompilerConfiguration(final Map<String, TargetInfo> targets) {
134
135
136
137 CommandLineCompilerConfiguration compilerConfig;
138
139
140
141 for (final TargetInfo targetInfo : targets.values()) {
142 final ProcessorConfiguration config = targetInfo.getConfiguration();
143
144
145
146 if (config instanceof CommandLineCompilerConfiguration) {
147 compilerConfig = (CommandLineCompilerConfiguration) config;
148 if (compilerConfig.getCompiler() instanceof MsvcCCompiler) {
149 return compilerConfig;
150 }
151 }
152 }
153 return null;
154 }
155
156
157
158
159
160
161
162
163 private File[] getSources(final List<File> sourceList) {
164 final File[] sortedSources = new File[sourceList.size()];
165 sourceList.toArray(sortedSources);
166 Arrays.sort(sortedSources, new Comparator<File>() {
167 @Override
168 public int compare(final File o1, final File o2) {
169 return o1.getName().compareTo(o2.getName());
170 }
171 });
172 return sortedSources;
173 }
174
175
176
177
178
179
180
181
182
183
184 private boolean isGroupMember(final String filter, final File candidate) {
185 final String fileName = candidate.getName();
186 final int lastDot = fileName.lastIndexOf('.');
187 if (lastDot >= 0 && lastDot < fileName.length() - 1) {
188 final String extension = ";" + fileName.substring(lastDot + 1).toLowerCase() + ";";
189 final String semiFilter = ";" + filter + ";";
190 return semiFilter.contains(extension);
191 }
192 return false;
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 private void writeCompileOptions(final Writer writer, final boolean isDebug, final String baseDir,
210 final CommandLineCompilerConfiguration compilerConfig) throws IOException {
211 final StringBuffer baseOptions = new StringBuffer(50);
212 baseOptions.append("# ADD BASE CPP");
213 final StringBuffer options = new StringBuffer(50);
214 options.append("# ADD CPP");
215 final File[] includePath = compilerConfig.getIncludePath();
216 for (final File element : includePath) {
217 options.append(" /I \"");
218 final String relPath = CUtil.getRelativePath(baseDir, element);
219 options.append(CUtil.toWindowsPath(relPath));
220 options.append('"');
221 }
222 final Hashtable<String, String> optionMap = new Hashtable<>();
223
224 if (isDebug) {
225
226
227
228 optionMap.put("/MT", "/MTd");
229 optionMap.put("/ML", "/MLd");
230 optionMap.put("/MD", "/MDd");
231 optionMap.put("/O2", "/Od");
232 optionMap.put("/O3", "/Od");
233 } else {
234
235
236
237 optionMap.put("/MTD", "/MT");
238 optionMap.put("/MLD", "/ML");
239 optionMap.put("/MDD", "/MD");
240 optionMap.put("/GM", "");
241 optionMap.put("/ZI", "");
242 optionMap.put("/OD", "/O2");
243 optionMap.put("/GZ", "");
244 }
245
246 final String[] preArgs = compilerConfig.getPreArguments();
247 for (final String preArg : preArgs) {
248 if (preArg.startsWith("/D")) {
249 options.append(" /D ");
250 baseOptions.append(" /D ");
251 final String body = preArg.substring(2);
252 if (preArg.indexOf('=') >= 0) {
253 options.append(body);
254 baseOptions.append(body);
255 } else {
256 final StringBuffer buf = new StringBuffer("\"");
257 if ("NDEBUG".equals(body) || "_DEBUG".equals(body)) {
258 if (isDebug) {
259 buf.append("_DEBUG");
260 } else {
261 buf.append("NDEBUG");
262 }
263 } else {
264 buf.append(body);
265 }
266 buf.append("\"");
267 options.append(buf);
268 baseOptions.append(buf);
269 }
270 } else if (!preArg.startsWith("/I")) {
271 String option = preArg;
272 final String key = option.toUpperCase(Locale.US);
273 if (optionMap.containsKey(key)) {
274 option = optionMap.get(key);
275 }
276 options.append(" ");
277 options.append(option);
278 baseOptions.append(" ");
279 baseOptions.append(option);
280 }
281 }
282 baseOptions.append("\r\n");
283 options.append("\r\n");
284 writer.write(baseOptions.toString());
285 writer.write(options.toString());
286
287 }
288
289 private void writeConfig(final Writer writer, final boolean isDebug, final List<DependencyDef> dependencies,
290 final String basePath, final CommandLineCompilerConfiguration compilerConfig, final TargetInfo linkTarget,
291 final Map<String, TargetInfo> targets) throws IOException {
292 writer.write("# PROP BASE Use_MFC 0\r\n");
293
294 String configType = "Release";
295 String configInt = "0";
296 String configMacro = "NDEBUG";
297 if (isDebug) {
298 configType = "Debug";
299 configInt = "1";
300 configMacro = "_DEBUG";
301 }
302
303 writer.write("# PROP BASE Use_Debug_Libraries ");
304 writer.write(configInt);
305 writer.write("\r\n# PROP BASE Output_Dir \"");
306 writer.write(configType);
307 writer.write("\"\r\n");
308 writer.write("# PROP BASE Intermediate_Dir \"");
309 writer.write(configType);
310 writer.write("\"\r\n");
311 writer.write("# PROP BASE Target_Dir \"\"\r\n");
312 writer.write("# PROP Use_MFC 0\r\n");
313 writer.write("# PROP Use_Debug_Libraries ");
314 writer.write(configInt);
315 writer.write("\r\n# PROP Output_Dir \"");
316 writer.write(configType);
317 writer.write("\"\r\n");
318 writer.write("# PROP Intermediate_Dir \"");
319 writer.write(configType);
320 writer.write("\"\r\n");
321 writer.write("# PROP Target_Dir \"\"\r\n");
322 writeCompileOptions(writer, isDebug, basePath, compilerConfig);
323 writer.write("# ADD BASE MTL /nologo /D \"" + configMacro + "\" /mktyplib203 /o NUL /win32\r\n");
324 writer.write("# ADD MTL /nologo /D \"" + configMacro + "\" /mktyplib203 /o NUL /win32\r\n");
325 writer.write("# ADD BASE RSC /l 0x409 /d \"" + configMacro + "\"\r\n");
326 writer.write("# ADD RSC /l 0x409 /d \"" + configMacro + "\"\r\n");
327 writer.write("BSC32=bscmake.exe\r\n");
328 writer.write("# ADD BASE BSC32 /nologo\r\n");
329 writer.write("# ADD BSC32 /nologo\r\n");
330 writer.write("LINK32=link.exe\r\n");
331 writeLinkOptions(writer, isDebug, dependencies, basePath, linkTarget, targets);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 private void writeLinkOptions(final Writer writer, final boolean isDebug, final List<DependencyDef> dependencies,
351 final String basePath, final TargetInfo linkTarget, final Map<String, TargetInfo> targets) throws IOException {
352
353 final StringBuffer baseOptions = new StringBuffer(100);
354 final StringBuffer options = new StringBuffer(100);
355 baseOptions.append("# ADD BASE LINK32");
356 options.append("# ADD LINK32");
357
358 final ProcessorConfiguration config = linkTarget.getConfiguration();
359 if (config instanceof CommandLineLinkerConfiguration) {
360 final CommandLineLinkerConfiguration linkConfig = (CommandLineLinkerConfiguration) config;
361
362 final File[] linkSources = linkTarget.getAllSources();
363 for (final File linkSource : linkSources) {
364
365
366
367 if (targets.get(linkSource.getName()) == null) {
368
369
370
371
372
373 String relPath = linkSource.getName();
374
375
376
377 boolean fromDependency = false;
378 if (relPath.indexOf(".") > 0) {
379 final String baseName = relPath.substring(0, relPath.indexOf("."));
380 for (final DependencyDef depend : dependencies) {
381 if (baseName.compareToIgnoreCase(depend.getName()) == 0) {
382 fromDependency = true;
383 }
384 }
385 }
386 if (!fromDependency) {
387 if (!CUtil.isSystemPath(linkSource)) {
388 relPath = CUtil.getRelativePath(basePath, linkSource);
389 }
390
391
392
393 if (relPath.indexOf(' ') > 0) {
394 options.append(" \"");
395 options.append(CUtil.toWindowsPath(relPath));
396 options.append("\"");
397 } else {
398 options.append(' ');
399 options.append(CUtil.toWindowsPath(relPath));
400 }
401 }
402 }
403 }
404 final String[] preArgs = linkConfig.getPreArguments();
405 for (final String preArg : preArgs) {
406 if (isDebug || !preArg.equals("/DEBUG")) {
407 options.append(' ');
408 options.append(preArg);
409 baseOptions.append(' ');
410 baseOptions.append(preArg);
411 }
412 }
413 final String[] endArgs = linkConfig.getEndArguments();
414 for (final String endArg : endArgs) {
415 options.append(' ');
416 options.append(endArg);
417 baseOptions.append(' ');
418 baseOptions.append(endArg);
419 }
420 }
421 baseOptions.append("\r\n");
422 options.append("\r\n");
423 writer.write(baseOptions.toString());
424 writer.write(options.toString());
425 }
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 private void writeMessage(final Writer writer, final String projectName, final String targtype) throws IOException {
441 writer.write("!MESSAGE This is not a valid makefile. ");
442 writer.write("To build this project using NMAKE,\r\n");
443 writer.write("!MESSAGE use the Export Makefile command and run\r\n");
444 writer.write("!MESSAGE \r\n");
445 writer.write("!MESSAGE NMAKE /f \"");
446 writer.write(projectName);
447 writer.write(".mak\".\r\n");
448 writer.write("!MESSAGE \r\n");
449 writer.write("!MESSAGE You can specify a configuration when running NMAKE\r\n");
450 writer.write("!MESSAGE by defining the macro CFG on the command line. ");
451 writer.write("For example:\r\n");
452 writer.write("!MESSAGE \r\n");
453 writer.write("!MESSAGE NMAKE /f \"");
454 writer.write(projectName);
455 writer.write(".mak\" CFG=\"");
456 writer.write(projectName);
457 writer.write(" - Win32 Debug\"\r\n");
458 writer.write("!MESSAGE \r\n");
459 writer.write("!MESSAGE Possible choices for configuration are:\r\n");
460 writer.write("!MESSAGE \r\n");
461 final String pattern = "!MESSAGE \"{0} - Win32 {1}\" (based on \"{2}\")\r\n";
462 writer.write(MessageFormat.format(pattern, new Object[] {
463 projectName, "Release", targtype
464 }));
465 writer.write(MessageFormat.format(pattern, new Object[] {
466 projectName, "Debug", targtype
467 }));
468 writer.write("!MESSAGE \r\n");
469 writer.write("\r\n");
470
471 }
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491 @Override
492 public void writeProject(final File fileName, final CCTask task, final ProjectDef projectDef, final List<File> files,
493 final Map<String, TargetInfo> targets, final TargetInfo linkTarget) throws IOException {
494
495
496
497
498
499 String projectName = projectDef.getName();
500 if (projectName != null) {
501 projectName = toProjectName(projectName);
502 } else {
503 projectName = toProjectName(fileName.getName());
504 }
505
506 final String basePath = fileName.getAbsoluteFile().getParent();
507
508 final File dspFile = new File(fileName + ".dsp");
509 if (!projectDef.getOverwrite() && dspFile.exists()) {
510 throw new BuildException("Not allowed to overwrite project file " + dspFile.toString());
511 }
512 final File dswFile = new File(fileName + ".dsw");
513 if (!projectDef.getOverwrite() && dswFile.exists()) {
514 throw new BuildException("Not allowed to overwrite project file " + dswFile.toString());
515 }
516
517 final CommandLineCompilerConfiguration compilerConfig = getBaseCompilerConfiguration(targets);
518 if (compilerConfig == null) {
519 throw new BuildException("Unable to generate Visual Studio project " + "when Microsoft C++ is not used.");
520 }
521
522 Writer writer = new BufferedWriter(new FileWriter(dspFile));
523 writer.write("# Microsoft Developer Studio Project File - Name=\"");
524 writer.write(projectName);
525 writer.write("\" - Package Owner=<4>\r\n");
526 writer.write("# Microsoft Developer Studio Generated Build File, Format Version ");
527 writer.write(this.version);
528 writer.write("\r\n");
529 writer.write("# ** DO NOT EDIT **\r\n\r\n");
530
531 writeComments(writer, projectDef.getComments());
532
533 final String outputType = task.getOuttype();
534 final String subsystem = task.getSubsystem();
535 String targtype = "Win32 (x86) Dynamic-Link Library";
536 String targid = "0x0102";
537 if ("executable".equals(outputType)) {
538 if ("console".equals(subsystem)) {
539 targtype = "Win32 (x86) Console Application";
540 targid = "0x0103";
541 } else {
542 targtype = "Win32 (x86) Application";
543 targid = "0x0101";
544 }
545 } else if ("static".equals(outputType)) {
546 targtype = "Win32 (x86) Static Library";
547 targid = "0x0104";
548 }
549 writer.write("# TARGTYPE \"");
550 writer.write(targtype);
551 writer.write("\" ");
552 writer.write(targid);
553 writer.write("\r\n\r\nCFG=");
554
555 writer.write(projectName + " - Win32 Debug");
556 writer.write("\r\n");
557
558 writeMessage(writer, projectName, targtype);
559
560 writer.write("# Begin Project\r\n");
561 if (this.version.equals("6.00")) {
562 writer.write("# PROP AllowPerConfigDependencies 0\r\n");
563 }
564 writer.write("# PROP Scc_ProjName \"\"\r\n");
565 writer.write("# PROP Scc_LocalPath \"\"\r\n");
566 writer.write("CPP=cl.exe\r\n");
567 writer.write("MTL=midl.exe\r\n");
568 writer.write("RSC=rc.exe\r\n");
569
570 writer.write("\r\n!IF \"$(CFG)\" == \"" + projectName + " - Win32 Release\"\r\n");
571
572 writeConfig(writer, false, projectDef.getDependencies(), basePath, compilerConfig, linkTarget, targets);
573
574 writer.write("\r\n!ELSEIF \"$(CFG)\" == \"" + projectName + " - Win32 Debug\"\r\n");
575
576 writeConfig(writer, true, projectDef.getDependencies(), basePath, compilerConfig, linkTarget, targets);
577
578 writer.write("\r\n!ENDIF\r\n");
579
580 writer.write("# Begin Target\r\n\r\n");
581 writer.write("# Name \"" + projectName + " - Win32 Release\"\r\n");
582 writer.write("# Name \"" + projectName + " - Win32 Debug\"\r\n");
583
584 final File[] sortedSources = getSources(files);
585
586 if (this.version.equals("6.00")) {
587 final String sourceFilter = "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat";
588 final String headerFilter = "h;hpp;hxx;hm;inl";
589 final String resourceFilter = "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe";
590
591 writer.write("# Begin Group \"Source Files\"\r\n\r\n");
592 writer.write("# PROP Default_Filter \"" + sourceFilter + "\"\r\n");
593
594 for (final File sortedSource1 : sortedSources) {
595 if (!isGroupMember(headerFilter, sortedSource1) && !isGroupMember(resourceFilter, sortedSource1)) {
596 writeSource(writer, basePath, sortedSource1);
597 }
598 }
599 writer.write("# End Group\r\n");
600
601 writer.write("# Begin Group \"Header Files\"\r\n\r\n");
602 writer.write("# PROP Default_Filter \"" + headerFilter + "\"\r\n");
603
604 for (final File sortedSource : sortedSources) {
605 if (isGroupMember(headerFilter, sortedSource)) {
606 writeSource(writer, basePath, sortedSource);
607 }
608 }
609 writer.write("# End Group\r\n");
610
611 writer.write("# Begin Group \"Resource Files\"\r\n\r\n");
612 writer.write("# PROP Default_Filter \"" + resourceFilter + "\"\r\n");
613
614 for (final File sortedSource : sortedSources) {
615 if (isGroupMember(resourceFilter, sortedSource)) {
616 writeSource(writer, basePath, sortedSource);
617 }
618 }
619 writer.write("# End Group\r\n");
620
621 } else {
622 for (final File sortedSource : sortedSources) {
623 writeSource(writer, basePath, sortedSource);
624 }
625 }
626
627 writer.write("# End Target\r\n");
628 writer.write("# End Project\r\n");
629 writer.close();
630
631
632
633
634 writer = new BufferedWriter(new FileWriter(dswFile));
635 writeWorkspace(writer, projectDef, projectName, dspFile);
636 writer.close();
637
638 }
639
640
641
642
643
644
645
646
647
648
649
650
651
652 private void writeSource(final Writer writer, final String basePath, final File groupMember) throws IOException {
653 writer.write("# Begin Source File\r\n\r\nSOURCE=");
654 String relativePath = CUtil.getRelativePath(basePath, groupMember);
655
656
657
658 if (!relativePath.startsWith(".") && !relativePath.contains(":") && !relativePath.startsWith("\\")) {
659 relativePath = ".\\" + relativePath;
660 }
661 writer.write(CUtil.toWindowsPath(relativePath));
662 writer.write("\r\n# End Source File\r\n");
663 }
664
665 private void writeWorkspace(final Writer writer, final ProjectDef project, final String projectName,
666 final File dspFile) throws IOException {
667
668 writer.write("Microsoft Developer Studio Workspace File, Format Version ");
669 writer.write(this.version);
670 writer.write("\r\n");
671 writer.write("# WARNING: DO NOT EDIT OR DELETE");
672 writer.write(" THIS WORKSPACE FILE!\r\n\r\n");
673
674 writeComments(writer, project.getComments());
675
676 final List<DependencyDef> dependencies = project.getDependencies();
677 final List<String> projectDeps = new ArrayList<>();
678 final String basePath = dspFile.getParent();
679 for (final DependencyDef dep : dependencies) {
680 if (dep.getFile() != null) {
681 final String projName = toProjectName(dep.getName());
682 projectDeps.add(projName);
683 final String depProject = CUtil
684 .toWindowsPath(CUtil.getRelativePath(basePath, new File(dep.getFile() + ".dsp")));
685 writeWorkspaceProject(writer, projName, depProject, dep.getDependsList());
686 }
687 }
688
689 writeWorkspaceProject(writer, projectName, dspFile.getName(), projectDeps);
690
691 writer.write("############################################");
692 writer.write("###################################\r\n\r\n");
693
694 writer.write("Global:\r\n\r\nPackage=<5>\r\n{{{\r\n}}}");
695 writer.write("\r\n\r\nPackage=<3>\r\n{{{\r\n}}}\r\n\r\n");
696
697 writer.write("########################################");
698 writer.write("#######################################\r\n\r\n");
699
700 }
701 }