View Javadoc
1   package org.codehaus.plexus.compiler.csharp;
2   
3   /*
4    * Copyright 2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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   * @author <a href="mailto:gdodinet@karmicsoft.com">Gilles Dodinet</a>
56   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
57   * @author <a href="mailto:matthew.pocock@ncl.ac.uk">Matthew Pocock</a>
58   * @author <a href="mailto:chris.stevenson@gmail.com">Chris Stevenson</a>
59   * @author <a href="mailto:mazas.marc@gmail.com">Marc Mazas</a>
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      // Compiler Implementation
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     $ mcs --help
182     Mono C# compiler, (C) 2001 - 2003 Ximian, Inc.
183     mcs [options] source-files
184        --about            About the Mono C# compiler
185        -addmodule:MODULE  Adds the module to the generated assembly
186        -checked[+|-]      Set default context to checked
187        -codepage:ID       Sets code page to the one in ID (number, utf8, reset)
188        -clscheck[+|-]     Disables CLS Compliance verifications
189        -define:S1[;S2]    Defines one or more symbols (short: /d:)
190        -debug[+|-], -g    Generate debugging information
191        -delaysign[+|-]    Only insert the public key into the assembly (no signing)
192        -doc:FILE          XML Documentation file to generate
193        -keycontainer:NAME The key pair container used to strongname the assembly
194        -keyfile:FILE      The strongname key file used to strongname the assembly
195        -langversion:TEXT  Specifies language version modes: ISO-1 or Default
196        -lib:PATH1,PATH2   Adds the paths to the assembly link path
197        -main:class        Specified the class that contains the entry point
198        -noconfig[+|-]     Disables implicit references to assemblies
199        -nostdlib[+|-]     Does not load core libraries
200        -nowarn:W1[,W2]    Disables one or more warnings
201        -optimize[+|-]     Enables code optimalizations
202        -out:FNAME         Specifies output file
203        -pkg:P1[,Pn]       References packages P1..Pn
204        -recurse:SPEC      Recursively compiles the files in SPEC ([dir]/file)
205        -reference:ASS     References the specified assembly (-r:ASS)
206        -target:KIND       Specifies the target (KIND is one of: exe, winexe,
207                           library, module), (short: /t:)
208        -unsafe[+|-]       Allows unsafe code
209        -warnaserror[+|-]  Treat warnings as errors
210        -warn:LEVEL        Sets warning level (the highest is 4, the default is 2)
211        -help2             Show other help flags
212 
213     Resources:
214        -linkresource:FILE[,ID] Links FILE as a resource
215        -resource:FILE[,ID]     Embed FILE as a resource
216        -win32res:FILE          Specifies Win32 resource file (.res)
217        -win32icon:FILE         Use this icon for the output
218        @file                   Read response file for more options
219 
220     Options can be of the form -option or /option
221         */
222 
223     /*
224     C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\Roslyn>csc -help -preferreduilang:en
225     Microsoft (R) Visual C# Compiler version 4.11.0-3.24460.3 (5649376e)
226     Copyright (C) Microsoft Corporation. All rights reserved.
227 
228 
229                                  Visual C# Compiler Options
230 
231                            - OUTPUT FILES -
232     -out:<file>                   Specify output file name (default: base name of
233                                   file with main class or first file)
234     -target:exe                   Build a console executable (default) (Short
235                                   form: -t:exe)
236     -target:winexe                Build a Windows executable (Short form:
237                                   -t:winexe)
238     -target:library               Build a library (Short form: -t:library)
239     -target:module                Build a module that can be added to another
240                                   assembly (Short form: -t:module)
241     -target:appcontainerexe       Build an Appcontainer executable (Short form:
242                                   -t:appcontainerexe)
243     -target:winmdobj              Build a Windows Runtime intermediate file that
244                                   is consumed by WinMDExp (Short form: -t:winmdobj)
245     -doc:<file>                   XML Documentation file to generate
246     -refout:<file>                Reference assembly output to generate
247     -platform:<string>            Limit which platforms this code can run on: x86,
248                                   Itanium, x64, arm, arm64, anycpu32bitpreferred, or
249                                   anycpu. The default is anycpu.
250 
251                            - INPUT FILES -
252     -recurse:<wildcard>           Include all files in the current directory and
253                                   subdirectories according to the wildcard
254                                   specifications
255     -reference:<alias>=<file>     Reference metadata from the specified assembly
256                                   file using the given alias (Short form: -r)
257     -reference:<file list>        Reference metadata from the specified assembly
258                                   files (Short form: -r)
259     -addmodule:<file list>        Link the specified modules into this assembly
260     -link:<file list>             Embed metadata from the specified interop
261                                   assembly files (Short form: -l)
262     -analyzer:<file list>         Run the analyzers from this assembly
263                                   (Short form: -a)
264     -additionalfile:<file list>   Additional files that don't directly affect code
265                                   generation but may be used by analyzers for producing
266                                   errors or warnings.
267     -embed                        Embed all source files in the PDB.
268     -embed:<file list>            Embed specific files in the PDB.
269 
270                            - RESOURCES -
271     -win32res:<file>              Specify a Win32 resource file (.res)
272     -win32icon:<file>             Use this icon for the output
273     -win32manifest:<file>         Specify a Win32 manifest file (.xml)
274     -nowin32manifest              Do not include the default Win32 manifest
275     -resource:<resinfo>           Embed the specified resource (Short form: -res)
276     -linkresource:<resinfo>       Link the specified resource to this assembly
277                                   (Short form: -linkres) Where the resinfo format
278                                   is <file>[,<string name>[,public|private]]
279 
280                            - CODE GENERATION -
281     -debug[+|-]                   Emit debugging information
282     -debug:{full|pdbonly|portable|embedded}
283                                   Specify debugging type ('full' is default,
284                                   'portable' is a cross-platform format,
285                                   'embedded' is a cross-platform format embedded into
286                                   the target .dll or .exe)
287     -optimize[+|-]                Enable optimizations (Short form: -o)
288     -deterministic                Produce a deterministic assembly
289                                   (including module version GUID and timestamp)
290     -refonly                      Produce a reference assembly in place of the main output
291     -instrument:TestCoverage      Produce an assembly instrumented to collect
292                                   coverage information
293     -sourcelink:<file>            Source link info to embed into PDB.
294 
295                            - ERRORS AND WARNINGS -
296     -warnaserror[+|-]             Report all warnings as errors
297     -warnaserror[+|-]:<warn list> Report specific warnings as errors
298                                   (use "nullable" for all nullability warnings)
299     -warn:<n>                     Set warning level (0 or higher) (Short form: -w)
300     -nowarn:<warn list>           Disable specific warning messages
301                                   (use "nullable" for all nullability warnings)
302     -ruleset:<file>               Specify a ruleset file that disables specific
303                                   diagnostics.
304     -errorlog:<file>[,version=<sarif_version>]
305                                   Specify a file to log all compiler and analyzer
306                                   diagnostics.
307                                   sarif_version:{1|2|2.1} Default is 1. 2 and 2.1
308                                   both mean SARIF version 2.1.0.
309     -reportanalyzer               Report additional analyzer information, such as
310                                   execution time.
311     -skipanalyzers[+|-]           Skip execution of diagnostic analyzers.
312 
313                            - LANGUAGE -
314     -checked[+|-]                 Generate overflow checks
315     -unsafe[+|-]                  Allow 'unsafe' code
316     -define:<symbol list>         Define conditional compilation symbol(s) (Short
317                                   form: -d)
318     -langversion:?                Display the allowed values for language version
319     -langversion:<string>         Specify language version such as
320                                   `latest` (latest version, including minor versions),
321                                   `default` (same as `latest`),
322                                   `latestmajor` (latest version, excluding minor versions),
323                                   `preview` (latest version, including features in unsupported preview),
324                                   or specific versions like `6` or `7.1`
325     -nullable[+|-]                Specify nullable context option enable|disable.
326     -nullable:{enable|disable|warnings|annotations}
327                                   Specify nullable context option enable|disable|warnings|annotations.
328 
329                            - SECURITY -
330     -delaysign[+|-]               Delay-sign the assembly using only the public
331                                   portion of the strong name key
332     -publicsign[+|-]              Public-sign the assembly using only the public
333                                   portion of the strong name key
334     -keyfile:<file>               Specify a strong name key file
335     -keycontainer:<string>        Specify a strong name key container
336     -highentropyva[+|-]           Enable high-entropy ASLR
337 
338                            - MISCELLANEOUS -
339     @<file>                       Read response file for more options
340     -help                         Display this usage message (Short form: -?)
341     -nologo                       Suppress compiler copyright message
342     -noconfig                     Do not auto include CSC.RSP file
343     -parallel[+|-]                Concurrent build.
344     -version                      Display the compiler version number and exit.
345 
346                            - ADVANCED -
347     -baseaddress:<address>        Base address for the library to be built
348     -checksumalgorithm:<alg>      Specify algorithm for calculating source file
349                                   checksum stored in PDB. Supported values are:
350                                   SHA1 or SHA256 (default).
351     -codepage:<n>                 Specify the codepage to use when opening source
352                                   files
353     -utf8output                   Output compiler messages in UTF-8 encoding
354     -main:<type>                  Specify the type that contains the entry point
355                                   (ignore all other possible entry points) (Short
356                                   form: -m)
357     -fullpaths                    Compiler generates fully qualified paths
358     -filealign:<n>                Specify the alignment used for output file
359                                   sections
360     -pathmap:<K1>=<V1>,<K2>=<V2>,...
361                                   Specify a mapping for source path names output by
362                                   the compiler.
363     -pdb:<file>                   Specify debug information file name (default:
364                                   output file name with .pdb extension)
365     -errorendlocation             Output line and column of the end location of
366                                   each error
367     -preferreduilang              Specify the preferred output language name.
368     -nosdkpath                    Disable searching the default SDK path for standard library assemblies.
369     -nostdlib[+|-]                Do not reference standard library (mscorlib.dll)
370     -subsystemversion:<string>    Specify subsystem version of this assembly
371     -lib:<file list>              Specify additional directories to search in for
372                                   references
373     -errorreport:<string>         Specify how to handle internal compiler errors:
374                                   prompt, send, queue, or none. The default is
375                                   queue.
376     -appconfig:<file>             Specify an application configuration file
377                                   containing assembly binding settings
378     -moduleassemblyname:<string>  Name of the assembly which this module will be
379                                   a part of
380     -modulename:<string>          Specify the name of the source module
381     -generatedfilesout:<dir>      Place files generated during compilation in the
382                                   specified directory.
383     -reportivts[+|-]                    Output information on all IVTs granted to this
384                                   assembly by all dependencies, and annotate foreign assembly
385                                   accessibility errors with what assembly they came from.
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         // config.isShowWarnings()
399         // config.getSourceVersion()
400         // config.getTargetVersion()
401         // config.getSourceEncoding()
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         // Main class
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         // Xml Doc output
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         // Nowarn option
461         // ----------------------------------------------------------------------
462 
463         String nowarn = compilerArguments.get("-nowarn");
464 
465         if (!StringUtils.isEmpty(nowarn)) {
466             args.add("/nowarn:" + nowarn);
467         }
468 
469         // ----------------------------------------------------------------------
470         // Out - Override output name, this is required for generating the unit test dll
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         // Resource File - compile in a resource file into the assembly being created
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         // Target - type of assembly to produce, lib,exe,winexe etc...
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         // remove MS logo from output (not applicable for mono)
505         // ----------------------------------------------------------------------
506         String nologo = compilerArguments.get("-nologo");
507 
508         if (!StringUtils.isEmpty(nologo)) {
509             args.add("/nologo");
510         }
511 
512         // ----------------------------------------------------------------------
513         // Unsafe option
514         // ----------------------------------------------------------------------
515         String unsafe = compilerArguments.get("-unsafe");
516 
517         if (!StringUtils.isEmpty(unsafe) && unsafe.equals("true")) {
518             args.add("/unsafe");
519         }
520 
521         // ----------------------------------------------------------------------
522         // PreferredUILang option
523         // ----------------------------------------------------------------------
524         String preferreduilang = compilerArguments.get("-preferreduilang");
525 
526         if (!StringUtils.isEmpty(preferreduilang)) {
527             args.add("/preferreduilang:" + preferreduilang);
528         }
529 
530         // ----------------------------------------------------------------------
531         // Utf8Output option
532         // ----------------------------------------------------------------------
533         String utf8output = compilerArguments.get("-utf8output");
534 
535         if (!StringUtils.isEmpty(utf8output)) {
536             args.add("/utf8output:");
537         }
538 
539         // ----------------------------------------------------------------------
540         // add any resource files
541         // ----------------------------------------------------------------------
542         this.addResourceArgs(config, args);
543 
544         // ----------------------------------------------------------------------
545         // add source files
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         // Build the @arguments file
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         // Execute!
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             // TODO: exception?
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     // added for debug purposes....
710     protected static String[] getSourceFiles(CompilerConfiguration config) {
711         Set<String> sources = new HashSet<>();
712 
713         // Set sourceFiles = null;
714         // was:
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 }