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 @Named("csharp")
61 public class CSharpCompiler extends AbstractCompiler {
62 private static final String JAR_SUFFIX = ".jar";
63 private static final String DLL_SUFFIX = ".dll";
64 private static final String NET_SUFFIX = ".net";
65
66 private static final String ARGUMENTS_FILE_NAME = "csharp-arguments";
67
68 private static final String[] DEFAULT_INCLUDES = {"**/**"};
69
70 private Map<String, String> compilerArguments;
71
72
73
74
75
76 public CSharpCompiler() {
77 super(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES, ".cs", null, null);
78 }
79
80
81
82
83
84 @Override
85 public String getCompilerId() {
86 return "csharp";
87 }
88
89 public boolean canUpdateTarget(CompilerConfiguration configuration) throws CompilerException {
90 return false;
91 }
92
93 public String getOutputFile(CompilerConfiguration configuration) throws CompilerException {
94 return configuration.getOutputFileName() + "." + getTypeExtension(configuration);
95 }
96
97 public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
98 File destinationDir = new File(config.getOutputLocation());
99
100 if (!destinationDir.exists()) {
101 destinationDir.mkdirs();
102 }
103
104 config.setSourceFiles(null);
105
106 String[] sourceFiles = CSharpCompiler.getSourceFiles(config);
107
108 if (sourceFiles.length == 0) {
109 return new CompilerResult().success(true);
110 }
111
112 logCompiling(sourceFiles, config);
113
114 String[] args = buildCompilerArguments(config, sourceFiles);
115
116 List<CompilerMessage> messages;
117
118 if (config.isFork()) {
119 messages = compileOutOfProcess(
120 config.getWorkingDirectory(), config.getBuildDirectory(), findExecutable(config), args);
121 } else {
122 throw new CompilerException("This compiler doesn't support in-process compilation.");
123 }
124
125 return new CompilerResult().compilerMessages(messages);
126 }
127
128 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
129 return buildCompilerArguments(config, CSharpCompiler.getSourceFiles(config));
130 }
131
132
133
134
135
136 private Map<String, String> getCompilerArguments(CompilerConfiguration config) {
137 if (compilerArguments != null) {
138 return compilerArguments;
139 }
140
141 compilerArguments = config.getCustomCompilerArgumentsAsMap();
142
143 Iterator<String> i = compilerArguments.keySet().iterator();
144
145 while (i.hasNext()) {
146 String orig = i.next();
147 String v = compilerArguments.get(orig);
148 if (orig.contains(":") && v == null) {
149 String[] arr = orig.split(":");
150 i.remove();
151 String k = arr[0];
152 v = arr[1];
153 compilerArguments.put(k, v);
154 if (config.isDebug()) {
155 System.out.println("transforming argument from " + orig + " to " + k + " = [" + v + "]");
156 }
157 }
158 }
159
160 config.setCustomCompilerArgumentsAsMap(compilerArguments);
161
162 return compilerArguments;
163 }
164
165 private String findExecutable(CompilerConfiguration config) {
166 String executable = config.getExecutable();
167
168 if (!StringUtils.isEmpty(executable)) {
169 return executable;
170 }
171
172 if (Os.isFamily("windows")) {
173 return "csc";
174 }
175
176 return "mcs";
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 private String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles)
223 throws CompilerException {
224 List<String> args = new ArrayList<>();
225
226 if (config.isDebug()) {
227 args.add("/debug+");
228 } else {
229 args.add("/debug-");
230 }
231
232
233
234
235
236
237
238
239
240
241 for (String element : config.getClasspathEntries()) {
242 File f = new File(element);
243
244 if (!f.isFile()) {
245 continue;
246 }
247
248 if (element.endsWith(JAR_SUFFIX)) {
249 try {
250 File dllDir = new File(element + NET_SUFFIX);
251 if (!dllDir.exists()) {
252 dllDir.mkdir();
253 }
254 JarUtil.extract(dllDir.toPath(), new File(element));
255 for (String tmpfile : dllDir.list()) {
256 if (tmpfile.endsWith(DLL_SUFFIX)) {
257 String dll =
258 Paths.get(dllDir.getAbsolutePath(), tmpfile).toString();
259 args.add("/reference:\"" + dll + "\"");
260 }
261 }
262 } catch (IOException e) {
263 throw new CompilerException(e.toString(), e);
264 }
265 } else {
266 args.add("/reference:\"" + element + "\"");
267 }
268 }
269
270
271
272
273
274 Map<String, String> compilerArguments = getCompilerArguments(config);
275
276 String mainClass = compilerArguments.get("-main");
277
278 if (!StringUtils.isEmpty(mainClass)) {
279 args.add("/main:" + mainClass);
280 }
281
282
283
284
285
286 String doc = compilerArguments.get("-doc");
287
288 if (!StringUtils.isEmpty(doc)) {
289 args.add("/doc:"
290 + new File(config.getOutputLocation(), config.getOutputFileName() + ".xml").getAbsolutePath());
291 }
292
293
294
295
296
297 String nowarn = compilerArguments.get("-nowarn");
298
299 if (!StringUtils.isEmpty(nowarn)) {
300 args.add("/nowarn:" + nowarn);
301 }
302
303
304
305
306
307 String out = compilerArguments.get("-out");
308
309 if (!StringUtils.isEmpty(out)) {
310 args.add("/out:" + new File(config.getOutputLocation(), out).getAbsolutePath());
311 } else {
312 args.add("/out:" + new File(config.getOutputLocation(), getOutputFile(config)).getAbsolutePath());
313 }
314
315
316
317
318 String resourcefile = compilerArguments.get("-resourcefile");
319
320 if (!StringUtils.isEmpty(resourcefile)) {
321 String resourceTarget = compilerArguments.get("-resourcetarget");
322 args.add("/res:" + new File(resourcefile).getAbsolutePath() + "," + resourceTarget);
323 }
324
325
326
327
328
329 String target = compilerArguments.get("-target");
330
331 if (StringUtils.isEmpty(target)) {
332 args.add("/target:library");
333 } else {
334 args.add("/target:" + target);
335 }
336
337
338
339
340 String nologo = compilerArguments.get("-nologo");
341
342 if (!StringUtils.isEmpty(nologo)) {
343 args.add("/nologo");
344 }
345
346
347
348
349 this.addResourceArgs(config, args);
350
351
352
353
354 for (String sourceFile : sourceFiles) {
355 args.add(sourceFile);
356 }
357
358 return args.toArray(new String[args.size()]);
359 }
360
361 private void addResourceArgs(CompilerConfiguration config, List<String> args) {
362 File filteredResourceDir = this.findResourceDir(config);
363 if ((filteredResourceDir != null) && filteredResourceDir.exists()) {
364 DirectoryScanner scanner = new DirectoryScanner();
365 scanner.setBasedir(filteredResourceDir);
366 scanner.setIncludes(DEFAULT_INCLUDES);
367 scanner.addDefaultExcludes();
368 scanner.scan();
369
370 List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
371 for (String name : includedFiles) {
372 File filteredResource = new File(filteredResourceDir, name);
373 String assemblyResourceName = this.convertNameToAssemblyResourceName(name);
374 String argLine = "/resource:\"" + filteredResource + "\",\"" + assemblyResourceName + "\"";
375 if (config.isDebug()) {
376 System.out.println("adding resource arg line:" + argLine);
377 }
378 args.add(argLine);
379 }
380 }
381 }
382
383 private File findResourceDir(CompilerConfiguration config) {
384 if (config.isDebug()) {
385 System.out.println("Looking for resourcesDir");
386 }
387
388 Map<String, String> compilerArguments = getCompilerArguments(config);
389
390 String tempResourcesDirAsString = compilerArguments.get("-resourceDir");
391 File filteredResourceDir = null;
392 if (tempResourcesDirAsString != null) {
393 filteredResourceDir = new File(tempResourcesDirAsString);
394 if (config.isDebug()) {
395 System.out.println("Found resourceDir at: " + filteredResourceDir.toString());
396 }
397 } else {
398 if (config.isDebug()) {
399 System.out.println("No resourceDir was available.");
400 }
401 }
402 return filteredResourceDir;
403 }
404
405 private String convertNameToAssemblyResourceName(String name) {
406 return name.replace(File.separatorChar, '.');
407 }
408
409 @SuppressWarnings("deprecation")
410 private List<CompilerMessage> compileOutOfProcess(
411 File workingDirectory, File target, String executable, String[] args) throws CompilerException {
412
413
414
415
416 File file;
417
418 PrintWriter output = null;
419
420 try {
421 file = new File(target, ARGUMENTS_FILE_NAME);
422
423 output = new PrintWriter(new FileWriter(file));
424
425 for (String arg : args) {
426 output.println(arg);
427 }
428 } catch (IOException e) {
429 throw new CompilerException("Error writing arguments file.", e);
430 } finally {
431 IOUtil.close(output);
432 }
433
434
435
436
437
438 Commandline cli = new Commandline();
439
440 cli.setWorkingDirectory(workingDirectory.getAbsolutePath());
441
442 cli.setExecutable(executable);
443
444 cli.createArgument().setValue("@" + file.getAbsolutePath());
445
446 Writer stringWriter = new StringWriter();
447
448 StreamConsumer out = new WriterStreamConsumer(stringWriter);
449
450 StreamConsumer err = new WriterStreamConsumer(stringWriter);
451
452 int returnCode;
453
454 List<CompilerMessage> messages;
455
456 try {
457 returnCode = CommandLineUtils.executeCommandLine(cli, out, err);
458
459 messages = parseCompilerOutput(new BufferedReader(new StringReader(stringWriter.toString())));
460 } catch (CommandLineException | IOException e) {
461 throw new CompilerException("Error while executing the external compiler.", e);
462 }
463
464 if (returnCode != 0 && messages.isEmpty()) {
465
466 messages.add(new CompilerMessage(
467 "Failure executing the compiler, but could not parse the error:" + EOL + stringWriter.toString(),
468 true));
469 }
470
471 return messages;
472 }
473
474 public static List<CompilerMessage> parseCompilerOutput(BufferedReader bufferedReader) throws IOException {
475 List<CompilerMessage> messages = new ArrayList<>();
476
477 String line = bufferedReader.readLine();
478
479 while (line != null) {
480 CompilerMessage compilerError = DefaultCSharpCompilerParser.parseLine(line);
481
482 if (compilerError != null) {
483 messages.add(compilerError);
484 }
485
486 line = bufferedReader.readLine();
487 }
488
489 return messages;
490 }
491
492 private String getType(Map<String, String> compilerArguments) {
493 String type = compilerArguments.get("-target");
494
495 if (StringUtils.isEmpty(type)) {
496 return "library";
497 }
498
499 return type;
500 }
501
502 private String getTypeExtension(CompilerConfiguration configuration) throws CompilerException {
503 String type = getType(configuration.getCustomCompilerArgumentsAsMap());
504
505 if ("exe".equals(type) || "winexe".equals(type)) {
506 return "exe";
507 }
508
509 if ("library".equals(type) || "module".equals(type)) {
510 return "dll";
511 }
512
513 throw new CompilerException("Unrecognized type '" + type + "'.");
514 }
515
516
517 protected static String[] getSourceFiles(CompilerConfiguration config) {
518 Set<String> sources = new HashSet<>();
519
520
521
522 Set<File> sourceFiles = config.getSourceFiles();
523
524 if (sourceFiles != null && !sourceFiles.isEmpty()) {
525 for (File sourceFile : sourceFiles) {
526 sources.add(sourceFile.getAbsolutePath());
527 }
528 } else {
529 for (String sourceLocation : config.getSourceLocations()) {
530 if (!new File(sourceLocation).exists()) {
531 if (config.isDebug()) {
532 System.out.println("Ignoring not found sourceLocation at: " + sourceLocation);
533 }
534 continue;
535 }
536 sources.addAll(getSourceFilesForSourceRoot(config, sourceLocation));
537 }
538 }
539
540 String[] result;
541
542 if (sources.isEmpty()) {
543 result = new String[0];
544 } else {
545 result = sources.toArray(new String[sources.size()]);
546 }
547
548 return result;
549 }
550
551 protected static Set<String> getSourceFilesForSourceRoot(CompilerConfiguration config, String sourceLocation) {
552 DirectoryScanner scanner = new DirectoryScanner();
553
554 scanner.setBasedir(sourceLocation);
555
556 Set<String> includes = config.getIncludes();
557
558 if (includes != null && !includes.isEmpty()) {
559 String[] inclStrs = includes.toArray(new String[includes.size()]);
560 scanner.setIncludes(inclStrs);
561 } else {
562 scanner.setIncludes(new String[] {"**/*.cs"});
563 }
564
565 Set<String> excludes = config.getExcludes();
566
567 if (excludes != null && !excludes.isEmpty()) {
568 String[] exclStrs = excludes.toArray(new String[excludes.size()]);
569 scanner.setIncludes(exclStrs);
570 }
571
572 scanner.scan();
573
574 String[] sourceDirectorySources = scanner.getIncludedFiles();
575
576 Set<String> sources = new HashSet<>();
577
578 for (String source : sourceDirectorySources) {
579 File f = new File(sourceLocation, source);
580
581 sources.add(f.getPath());
582 }
583
584 return sources;
585 }
586 }