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.Arrays;
32 import java.util.HashSet;
33 import java.util.Iterator;
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 private Map<String, String> compilerArguments;
72
73
74
75
76
77 public CSharpCompiler() {
78 super(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES, ".cs", null, null);
79 }
80
81
82
83
84
85 @Override
86 public String getCompilerId() {
87 return "csharp";
88 }
89
90 public boolean canUpdateTarget(CompilerConfiguration configuration) throws CompilerException {
91 return false;
92 }
93
94 public String getOutputFile(CompilerConfiguration configuration) throws CompilerException {
95 return configuration.getOutputFileName() + "." + getTypeExtension(configuration);
96 }
97
98 public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
99 File destinationDir = new File(config.getOutputLocation());
100
101 if (!destinationDir.exists()) {
102 destinationDir.mkdirs();
103 }
104
105 config.setSourceFiles(null);
106
107 String[] sourceFiles = CSharpCompiler.getSourceFiles(config);
108
109 if (sourceFiles.length == 0) {
110 return new CompilerResult().success(true);
111 }
112
113 logCompiling(sourceFiles, config);
114
115 String[] args = buildCompilerArguments(config, sourceFiles);
116
117 List<CompilerMessage> messages;
118
119 if (config.isFork()) {
120 messages = compileOutOfProcess(
121 config.getWorkingDirectory(), config.getBuildDirectory(), findExecutable(config), args);
122 } else {
123 throw new CompilerException("This compiler doesn't support in-process compilation.");
124 }
125
126 return new CompilerResult().compilerMessages(messages);
127 }
128
129 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
130 return buildCompilerArguments(config, CSharpCompiler.getSourceFiles(config));
131 }
132
133
134
135
136
137 private Map<String, String> getCompilerArguments(CompilerConfiguration config) {
138 if (compilerArguments != null) {
139 return compilerArguments;
140 }
141
142 compilerArguments = config.getCustomCompilerArgumentsAsMap();
143
144 Iterator<String> i = compilerArguments.keySet().iterator();
145
146 while (i.hasNext()) {
147 String orig = i.next();
148 String v = compilerArguments.get(orig);
149 if (orig.contains(":") && v == null) {
150 String[] arr = orig.split(":");
151 i.remove();
152 String k = arr[0];
153 v = arr[1];
154 compilerArguments.put(k, v);
155 if (config.isDebug()) {
156 System.out.println("transforming argument from " + orig + " to " + k + " = [" + v + "]");
157 }
158 }
159 }
160
161 config.setCustomCompilerArgumentsAsMap(compilerArguments);
162
163 return compilerArguments;
164 }
165
166 private String findExecutable(CompilerConfiguration config) {
167 String executable = config.getExecutable();
168
169 if (!StringUtils.isEmpty(executable)) {
170 return executable;
171 }
172
173 if (Os.isFamily("windows")) {
174 return "csc";
175 }
176
177 return "mcs";
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 private String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles)
389 throws CompilerException {
390 List<String> args = new ArrayList<>();
391
392 if (config.isDebug()) {
393 args.add("/debug+");
394 } else {
395 args.add("/debug-");
396 }
397
398
399
400
401
402
403
404
405
406
407 for (String element : config.getClasspathEntries()) {
408 File f = new File(element);
409
410 if (!f.isFile()) {
411 continue;
412 }
413
414 if (element.endsWith(JAR_SUFFIX)) {
415 try {
416 File dllDir = new File(element + NET_SUFFIX);
417 if (!dllDir.exists()) {
418 dllDir.mkdir();
419 }
420 JarUtil.extract(dllDir.toPath(), new File(element));
421 for (String tmpfile : dllDir.list()) {
422 if (tmpfile.endsWith(DLL_SUFFIX)) {
423 String dll =
424 Paths.get(dllDir.getAbsolutePath(), tmpfile).toString();
425 args.add("/reference:\"" + dll + "\"");
426 }
427 }
428 } catch (IOException e) {
429 throw new CompilerException(e.toString(), e);
430 }
431 } else {
432 args.add("/reference:\"" + element + "\"");
433 }
434 }
435
436
437
438
439
440 Map<String, String> compilerArguments = getCompilerArguments(config);
441
442 String mainClass = compilerArguments.get("-main");
443
444 if (!StringUtils.isEmpty(mainClass)) {
445 args.add("/main:" + mainClass);
446 }
447
448
449
450
451
452 String doc = compilerArguments.get("-doc");
453
454 if (!StringUtils.isEmpty(doc)) {
455 args.add("/doc:"
456 + new File(config.getOutputLocation(), config.getOutputFileName() + ".xml").getAbsolutePath());
457 }
458
459
460
461
462
463 String nowarn = compilerArguments.get("-nowarn");
464
465 if (!StringUtils.isEmpty(nowarn)) {
466 args.add("/nowarn:" + nowarn);
467 }
468
469
470
471
472
473 String out = compilerArguments.get("-out");
474
475 if (!StringUtils.isEmpty(out)) {
476 args.add("/out:" + new File(config.getOutputLocation(), out).getAbsolutePath());
477 } else {
478 args.add("/out:" + new File(config.getOutputLocation(), getOutputFile(config)).getAbsolutePath());
479 }
480
481
482
483
484 String resourcefile = compilerArguments.get("-resourcefile");
485
486 if (!StringUtils.isEmpty(resourcefile)) {
487 String resourceTarget = compilerArguments.get("-resourcetarget");
488 args.add("/res:" + new File(resourcefile).getAbsolutePath() + "," + resourceTarget);
489 }
490
491
492
493
494
495 String target = compilerArguments.get("-target");
496
497 if (StringUtils.isEmpty(target)) {
498 args.add("/target:library");
499 } else {
500 args.add("/target:" + target);
501 }
502
503
504
505
506 String nologo = compilerArguments.get("-nologo");
507
508 if (!StringUtils.isEmpty(nologo)) {
509 args.add("/nologo");
510 }
511
512
513
514
515 String unsafe = compilerArguments.get("-unsafe");
516
517 if (!StringUtils.isEmpty(unsafe) && unsafe.equals("true")) {
518 args.add("/unsafe");
519 }
520
521
522
523
524 String preferreduilang = compilerArguments.get("-preferreduilang");
525
526 if (!StringUtils.isEmpty(preferreduilang)) {
527 args.add("/preferreduilang:" + preferreduilang);
528 }
529
530
531
532
533 String utf8output = compilerArguments.get("-utf8output");
534
535 if (!StringUtils.isEmpty(utf8output)) {
536 args.add("/utf8output:");
537 }
538
539
540
541
542 this.addResourceArgs(config, args);
543
544
545
546
547 for (String sourceFile : sourceFiles) {
548 args.add(sourceFile);
549 }
550
551 return args.toArray(new String[args.size()]);
552 }
553
554 private void addResourceArgs(CompilerConfiguration config, List<String> args) {
555 File filteredResourceDir = this.findResourceDir(config);
556 if ((filteredResourceDir != null) && filteredResourceDir.exists()) {
557 DirectoryScanner scanner = new DirectoryScanner();
558 scanner.setBasedir(filteredResourceDir);
559 scanner.setIncludes(DEFAULT_INCLUDES);
560 scanner.addDefaultExcludes();
561 scanner.scan();
562
563 List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
564 for (String name : includedFiles) {
565 File filteredResource = new File(filteredResourceDir, name);
566 String assemblyResourceName = this.convertNameToAssemblyResourceName(name);
567 String argLine = "/resource:\"" + filteredResource + "\",\"" + assemblyResourceName + "\"";
568 if (config.isDebug()) {
569 System.out.println("adding resource arg line:" + argLine);
570 }
571 args.add(argLine);
572 }
573 }
574 }
575
576 private File findResourceDir(CompilerConfiguration config) {
577 if (config.isDebug()) {
578 System.out.println("Looking for resourcesDir");
579 }
580
581 Map<String, String> compilerArguments = getCompilerArguments(config);
582
583 String tempResourcesDirAsString = compilerArguments.get("-resourceDir");
584 File filteredResourceDir = null;
585 if (tempResourcesDirAsString != null) {
586 filteredResourceDir = new File(tempResourcesDirAsString);
587 if (config.isDebug()) {
588 System.out.println("Found resourceDir at: " + filteredResourceDir.toString());
589 }
590 } else {
591 if (config.isDebug()) {
592 System.out.println("No resourceDir was available.");
593 }
594 }
595 return filteredResourceDir;
596 }
597
598 private String convertNameToAssemblyResourceName(String name) {
599 return name.replace(File.separatorChar, '.');
600 }
601
602 @SuppressWarnings("deprecation")
603 private List<CompilerMessage> compileOutOfProcess(
604 File workingDirectory, File target, String executable, String[] args) throws CompilerException {
605
606
607
608
609 File file;
610
611 PrintWriter output = null;
612
613 try {
614 file = new File(target, ARGUMENTS_FILE_NAME);
615
616 output = new PrintWriter(new FileWriter(file));
617
618 for (String arg : args) {
619 output.println(arg);
620 }
621 } catch (IOException e) {
622 throw new CompilerException("Error writing arguments file.", e);
623 } finally {
624 IOUtil.close(output);
625 }
626
627
628
629
630
631 Commandline cli = new Commandline();
632
633 cli.setWorkingDirectory(workingDirectory.getAbsolutePath());
634
635 cli.setExecutable(executable);
636
637 cli.createArgument().setValue("@" + file.getAbsolutePath());
638
639 Writer stringWriter = new StringWriter();
640
641 StreamConsumer out = new WriterStreamConsumer(stringWriter);
642
643 StreamConsumer err = new WriterStreamConsumer(stringWriter);
644
645 int returnCode;
646
647 List<CompilerMessage> messages;
648
649 try {
650 returnCode = CommandLineUtils.executeCommandLine(cli, out, err);
651
652 messages = parseCompilerOutput(new BufferedReader(new StringReader(stringWriter.toString())));
653 } catch (CommandLineException | IOException e) {
654 throw new CompilerException("Error while executing the external compiler.", e);
655 }
656
657 if (returnCode != 0 && messages.isEmpty()) {
658
659 messages.add(new CompilerMessage(
660 "Failure executing the compiler, but could not parse the error:" + EOL + stringWriter.toString(),
661 true));
662 }
663
664 return messages;
665 }
666
667 public static List<CompilerMessage> parseCompilerOutput(BufferedReader bufferedReader) throws IOException {
668 List<CompilerMessage> messages = new ArrayList<>();
669
670 String line = bufferedReader.readLine();
671
672 while (line != null) {
673 CompilerMessage compilerError = DefaultCSharpCompilerParser.parseLine(line);
674
675 if (compilerError != null) {
676 messages.add(compilerError);
677 }
678
679 line = bufferedReader.readLine();
680 }
681
682 return messages;
683 }
684
685 private String getType(Map<String, String> compilerArguments) {
686 String type = compilerArguments.get("-target");
687
688 if (StringUtils.isEmpty(type)) {
689 return "library";
690 }
691
692 return type;
693 }
694
695 private String getTypeExtension(CompilerConfiguration configuration) throws CompilerException {
696 String type = getType(configuration.getCustomCompilerArgumentsAsMap());
697
698 if ("exe".equals(type) || "winexe".equals(type)) {
699 return "exe";
700 }
701
702 if ("library".equals(type) || "module".equals(type)) {
703 return "dll";
704 }
705
706 throw new CompilerException("Unrecognized type '" + type + "'.");
707 }
708
709
710 protected static String[] getSourceFiles(CompilerConfiguration config) {
711 Set<String> sources = new HashSet<>();
712
713
714
715 Set<File> sourceFiles = config.getSourceFiles();
716
717 if (sourceFiles != null && !sourceFiles.isEmpty()) {
718 for (File sourceFile : sourceFiles) {
719 sources.add(sourceFile.getAbsolutePath());
720 }
721 } else {
722 for (String sourceLocation : config.getSourceLocations()) {
723 if (!new File(sourceLocation).exists()) {
724 if (config.isDebug()) {
725 System.out.println("Ignoring not found sourceLocation at: " + sourceLocation);
726 }
727 continue;
728 }
729 sources.addAll(getSourceFilesForSourceRoot(config, sourceLocation));
730 }
731 }
732
733 String[] result;
734
735 if (sources.isEmpty()) {
736 result = new String[0];
737 } else {
738 result = sources.toArray(new String[sources.size()]);
739 }
740
741 return result;
742 }
743
744 protected static Set<String> getSourceFilesForSourceRoot(CompilerConfiguration config, String sourceLocation) {
745 DirectoryScanner scanner = new DirectoryScanner();
746
747 scanner.setBasedir(sourceLocation);
748
749 Set<String> includes = config.getIncludes();
750
751 if (includes != null && !includes.isEmpty()) {
752 String[] inclStrs = includes.toArray(new String[includes.size()]);
753 scanner.setIncludes(inclStrs);
754 } else {
755 scanner.setIncludes(new String[] {"**/*.cs"});
756 }
757
758 Set<String> excludes = config.getExcludes();
759
760 if (excludes != null && !excludes.isEmpty()) {
761 String[] exclStrs = excludes.toArray(new String[excludes.size()]);
762 scanner.setExcludes(exclStrs);
763 }
764
765 scanner.scan();
766
767 String[] sourceDirectorySources = scanner.getIncludedFiles();
768
769 Set<String> sources = new HashSet<>();
770
771 for (String source : sourceDirectorySources) {
772 File f = new File(sourceLocation, source);
773
774 sources.add(f.getPath());
775 }
776
777 return sources;
778 }
779 }