1 package org.codehaus.plexus.compiler.csharp;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import javax.inject.Named;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileWriter;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.io.StringReader;
27 import java.io.StringWriter;
28 import java.io.Writer;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import org.codehaus.plexus.compiler.AbstractCompiler;
39 import org.codehaus.plexus.compiler.CompilerConfiguration;
40 import org.codehaus.plexus.compiler.CompilerException;
41 import org.codehaus.plexus.compiler.CompilerMessage;
42 import org.codehaus.plexus.compiler.CompilerOutputStyle;
43 import org.codehaus.plexus.compiler.CompilerResult;
44 import org.codehaus.plexus.util.DirectoryScanner;
45 import org.codehaus.plexus.util.IOUtil;
46 import org.codehaus.plexus.util.Os;
47 import org.codehaus.plexus.util.StringUtils;
48 import org.codehaus.plexus.util.cli.CommandLineException;
49 import org.codehaus.plexus.util.cli.CommandLineUtils;
50 import org.codehaus.plexus.util.cli.Commandline;
51 import org.codehaus.plexus.util.cli.StreamConsumer;
52 import org.codehaus.plexus.util.cli.WriterStreamConsumer;
53
54
55
56
57
58
59
60
61 @Named("csharp")
62 public class CSharpCompiler extends AbstractCompiler {
63 private static final String JAR_SUFFIX = ".jar";
64 private static final String DLL_SUFFIX = ".dll";
65 private static final String NET_SUFFIX = ".net";
66
67 private static final String ARGUMENTS_FILE_NAME = "csharp-arguments";
68
69 private static final String[] DEFAULT_INCLUDES = {"**/**"};
70
71 public CSharpCompiler() {
72 super(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES, ".cs", null, null);
73 }
74
75 @Override
76 public String getCompilerId() {
77 return "csharp";
78 }
79
80 public boolean canUpdateTarget(CompilerConfiguration configuration) {
81 return false;
82 }
83
84 public String getOutputFile(CompilerConfiguration configuration) throws CompilerException {
85 return configuration.getOutputFileName() + "." + getTypeExtension(configuration);
86 }
87
88 public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
89 File destinationDir = new File(config.getOutputLocation());
90
91 if (!destinationDir.exists()) {
92 destinationDir.mkdirs();
93 }
94
95 config.setSourceFiles(null);
96
97 String[] sourceFiles = CSharpCompiler.getSourceFiles(config);
98
99 if (sourceFiles.length == 0) {
100 return new CompilerResult().success(true);
101 }
102
103 logCompiling(sourceFiles, config);
104
105 String[] args = buildCompilerArguments(config, sourceFiles);
106
107 List<CompilerMessage> messages;
108
109 if (config.isFork()) {
110 messages = compileOutOfProcess(
111 config.getWorkingDirectory(), config.getBuildDirectory(), findExecutable(config), args);
112 } else {
113 throw new CompilerException("This compiler doesn't support in-process compilation.");
114 }
115
116 return new CompilerResult().compilerMessages(messages);
117 }
118
119 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
120 return buildCompilerArguments(config, CSharpCompiler.getSourceFiles(config));
121 }
122
123
124
125
126
127
128
129
130
131 Map<String, String> getCompilerArguments(CompilerConfiguration config) {
132 Map<String, String> customArgs = config.getCustomCompilerArgumentsAsMap();
133 Map<String, String> normalizedArgs = new HashMap<>();
134
135 for (Map.Entry<String, String> entry : customArgs.entrySet()) {
136 String key = entry.getKey();
137 String value = entry.getValue();
138
139
140 if (value == null && key.contains(":")) {
141 int colonIndex = key.indexOf(':');
142 String actualKey = key.substring(0, colonIndex);
143 String actualValue = key.substring(colonIndex + 1);
144 normalizedArgs.put(actualKey, actualValue);
145
146 if (config.isDebug()) {
147 System.out.println("Normalized argument '" + key + "' to key='" + actualKey + "', value='"
148 + actualValue + "'");
149 }
150 } else {
151 normalizedArgs.put(key, value);
152 }
153 }
154
155 return normalizedArgs;
156 }
157
158 private String findExecutable(CompilerConfiguration config) {
159 String executable = config.getExecutable();
160
161 if (!StringUtils.isEmpty(executable)) {
162 return executable;
163 }
164
165 if (Os.isFamily("windows")) {
166 return "csc";
167 }
168
169 return "mcs";
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388 String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles) throws CompilerException {
389 List<String> args = new ArrayList<>();
390
391
392
393
394
395
396 for (String element : config.getClasspathEntries()) {
397 File f = new File(element);
398
399 if (!f.isFile()) {
400 continue;
401 }
402
403 if (element.endsWith(JAR_SUFFIX)) {
404 try {
405 File dllDir = new File(element + NET_SUFFIX);
406 if (!dllDir.exists()) {
407 dllDir.mkdir();
408 }
409 JarUtil.extract(dllDir.toPath(), new File(element));
410 for (String tmpfile : dllDir.list()) {
411 if (tmpfile.endsWith(DLL_SUFFIX)) {
412 String dll =
413 Paths.get(dllDir.getAbsolutePath(), tmpfile).toString();
414 args.add("/reference:\"" + dll + "\"");
415 }
416 }
417 } catch (IOException e) {
418 throw new CompilerException(e.toString(), e);
419 }
420 } else {
421 args.add("/reference:\"" + element + "\"");
422 }
423 }
424
425
426 Map<String, String> compilerArguments = getCompilerArguments(config);
427
428 String mainClass = compilerArguments.get("-main");
429 if (!StringUtils.isEmpty(mainClass)) {
430 args.add("/main:" + mainClass);
431 }
432
433
434 String doc = compilerArguments.get("-doc");
435 if (!StringUtils.isEmpty(doc)) {
436 args.add("/doc:"
437 + new File(config.getOutputLocation(), config.getOutputFileName() + ".xml").getAbsolutePath());
438 }
439
440
441 String debug = compilerArguments.get("-debug");
442 if (!StringUtils.isEmpty(debug)) {
443 args.add("/debug:" + debug);
444 }
445
446
447 String nowarn = compilerArguments.get("-nowarn");
448 if (!StringUtils.isEmpty(nowarn)) {
449 args.add("/nowarn:" + nowarn);
450 }
451
452
453 String out = compilerArguments.get("-out");
454 if (!StringUtils.isEmpty(out)) {
455 args.add("/out:" + new File(config.getOutputLocation(), out).getAbsolutePath());
456 } else {
457 args.add("/out:" + new File(config.getOutputLocation(), getOutputFile(config)).getAbsolutePath());
458 }
459
460
461 String resourcefile = compilerArguments.get("-resourcefile");
462 if (!StringUtils.isEmpty(resourcefile)) {
463 String resourceTarget = compilerArguments.get("-resourcetarget");
464 args.add("/res:" + new File(resourcefile).getAbsolutePath() + "," + resourceTarget);
465 }
466
467
468 String target = compilerArguments.get("-target");
469 if (StringUtils.isEmpty(target)) {
470 args.add("/target:library");
471 } else {
472 args.add("/target:" + target);
473 }
474
475
476 String nologo = compilerArguments.get("-nologo");
477 if (!StringUtils.isEmpty(nologo) && !"false".equalsIgnoreCase(nologo)) {
478 args.add("/nologo");
479 }
480
481
482 String unsafe = compilerArguments.get("-unsafe");
483 if (!StringUtils.isEmpty(unsafe) && "true".equalsIgnoreCase(unsafe)) {
484 args.add("/unsafe");
485 }
486
487
488 String preferreduilang = compilerArguments.get("-preferreduilang");
489 if (!StringUtils.isEmpty(preferreduilang)) {
490 args.add("/preferreduilang:" + preferreduilang);
491 }
492
493
494 String utf8output = compilerArguments.get("-utf8output");
495 if (!StringUtils.isEmpty(utf8output) && !"false".equals(utf8output)) {
496 args.add("/utf8output");
497 }
498
499
500 this.addResourceArgs(config, args);
501
502
503 Collections.addAll(args, sourceFiles);
504
505 if (config.isDebug()) {
506 System.out.println("built compiler arguments:" + args);
507 }
508
509 return args.toArray(new String[0]);
510 }
511
512 private void addResourceArgs(CompilerConfiguration config, List<String> args) {
513 File filteredResourceDir = this.findResourceDir(config);
514 if ((filteredResourceDir != null) && filteredResourceDir.exists()) {
515 DirectoryScanner scanner = new DirectoryScanner();
516 scanner.setBasedir(filteredResourceDir);
517 scanner.setIncludes(DEFAULT_INCLUDES);
518 scanner.addDefaultExcludes();
519 scanner.scan();
520
521 String[] includedFiles = scanner.getIncludedFiles();
522 for (String name : includedFiles) {
523 File filteredResource = new File(filteredResourceDir, name);
524 String assemblyResourceName = this.convertNameToAssemblyResourceName(name);
525 String argLine = "/resource:\"" + filteredResource + "\",\"" + assemblyResourceName + "\"";
526 if (config.isDebug()) {
527 System.out.println("adding resource arg line:" + argLine);
528 }
529 args.add(argLine);
530 }
531 }
532 }
533
534 private File findResourceDir(CompilerConfiguration config) {
535 if (config.isDebug()) {
536 System.out.println("Looking for resourcesDir");
537 }
538
539 Map<String, String> compilerArguments = getCompilerArguments(config);
540
541 String tempResourcesDirAsString = compilerArguments.get("-resourceDir");
542 File filteredResourceDir = null;
543 if (tempResourcesDirAsString != null) {
544 filteredResourceDir = new File(tempResourcesDirAsString);
545 if (config.isDebug()) {
546 System.out.println("Found resourceDir at: " + filteredResourceDir);
547 }
548 } else {
549 if (config.isDebug()) {
550 System.out.println("No resourceDir was available.");
551 }
552 }
553 return filteredResourceDir;
554 }
555
556 private String convertNameToAssemblyResourceName(String name) {
557 return name.replace(File.separatorChar, '.');
558 }
559
560 @SuppressWarnings("deprecation")
561 private List<CompilerMessage> compileOutOfProcess(
562 File workingDirectory, File target, String executable, String[] args) throws CompilerException {
563
564
565
566
567 File file;
568
569 PrintWriter output = null;
570
571 try {
572 file = new File(target, ARGUMENTS_FILE_NAME);
573
574 output = new PrintWriter(new FileWriter(file));
575
576 for (String arg : args) {
577 output.println(arg);
578 }
579 } catch (IOException e) {
580 throw new CompilerException("Error writing arguments file.", e);
581 } finally {
582 IOUtil.close(output);
583 }
584
585
586
587
588
589 Commandline cli = new Commandline();
590
591 cli.setWorkingDirectory(workingDirectory.getAbsolutePath());
592
593 cli.setExecutable(executable);
594
595 cli.createArgument().setValue("@" + file.getAbsolutePath());
596
597 Writer stringWriter = new StringWriter();
598
599 StreamConsumer out = new WriterStreamConsumer(stringWriter);
600
601 StreamConsumer err = new WriterStreamConsumer(stringWriter);
602
603 int returnCode;
604
605 List<CompilerMessage> messages;
606
607 try {
608 returnCode = CommandLineUtils.executeCommandLine(cli, out, err);
609
610 messages = parseCompilerOutput(new BufferedReader(new StringReader(stringWriter.toString())));
611 } catch (CommandLineException | IOException e) {
612 throw new CompilerException("Error while executing the external compiler.", e);
613 }
614
615 if (returnCode != 0 && messages.isEmpty()) {
616
617 messages.add(new CompilerMessage(
618 "Failure executing the compiler, but could not parse the error:" + EOL + stringWriter.toString(),
619 true));
620 }
621
622 return messages;
623 }
624
625 public static List<CompilerMessage> parseCompilerOutput(BufferedReader bufferedReader) throws IOException {
626 List<CompilerMessage> messages = new ArrayList<>();
627
628 String line = bufferedReader.readLine();
629
630 while (line != null) {
631 CompilerMessage compilerError = DefaultCSharpCompilerParser.parseLine(line);
632
633 if (compilerError != null) {
634 messages.add(compilerError);
635 }
636
637 line = bufferedReader.readLine();
638 }
639
640 return messages;
641 }
642
643 private String getType(Map<String, String> compilerArguments) {
644 String type = compilerArguments.get("-target");
645
646 if (StringUtils.isEmpty(type)) {
647 return "library";
648 }
649
650 return type;
651 }
652
653 private String getTypeExtension(CompilerConfiguration configuration) throws CompilerException {
654 String type = getType(configuration.getCustomCompilerArgumentsAsMap());
655
656 if ("exe".equals(type) || "winexe".equals(type)) {
657 return "exe";
658 }
659
660 if ("library".equals(type) || "module".equals(type)) {
661 return "dll";
662 }
663
664 throw new CompilerException("Unrecognized type '" + type + "'.");
665 }
666
667
668 protected static String[] getSourceFiles(CompilerConfiguration config) {
669 Set<String> sources = new HashSet<>();
670
671
672
673 Set<File> sourceFiles = config.getSourceFiles();
674
675 if (sourceFiles != null && !sourceFiles.isEmpty()) {
676 for (File sourceFile : sourceFiles) {
677 sources.add(sourceFile.getAbsolutePath());
678 }
679 } else {
680 for (String sourceLocation : config.getSourceLocations()) {
681 if (!new File(sourceLocation).exists()) {
682 if (config.isDebug()) {
683 System.out.println("Ignoring not found sourceLocation at: " + sourceLocation);
684 }
685 continue;
686 }
687 sources.addAll(getSourceFilesForSourceRoot(config, sourceLocation));
688 }
689 }
690
691 String[] result;
692
693 if (sources.isEmpty()) {
694 result = new String[0];
695 } else {
696 result = sources.toArray(new String[sources.size()]);
697 }
698
699 return result;
700 }
701
702 protected static Set<String> getSourceFilesForSourceRoot(CompilerConfiguration config, String sourceLocation) {
703 DirectoryScanner scanner = new DirectoryScanner();
704
705 scanner.setBasedir(sourceLocation);
706
707 Set<String> includes = config.getIncludes();
708
709 if (includes != null && !includes.isEmpty()) {
710 String[] inclStrs = includes.toArray(new String[includes.size()]);
711 scanner.setIncludes(inclStrs);
712 } else {
713 scanner.setIncludes(new String[] {"**/*.cs"});
714 }
715
716 Set<String> excludes = config.getExcludes();
717
718 if (excludes != null && !excludes.isEmpty()) {
719 String[] exclStrs = excludes.toArray(new String[excludes.size()]);
720 scanner.setExcludes(exclStrs);
721 }
722
723 scanner.scan();
724
725 String[] sourceDirectorySources = scanner.getIncludedFiles();
726
727 Set<String> sources = new HashSet<>();
728
729 for (String source : sourceDirectorySources) {
730 File f = new File(sourceLocation, source);
731
732 sources.add(f.getPath());
733 }
734
735 return sources;
736 }
737 }