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.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   * @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      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      * Parse compiler arguments and normalize legacy colon-separated format.
125      * Converts arguments like "-main:MyClass" (stored as key with null value)
126      * into proper key-value pairs: "-main" -> "MyClass".
127      *
128      * @param config the compiler configuration
129      * @return normalized map of compiler arguments
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             // Handle legacy format: "-main:MyClass" stored as key with null value
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     $ mcs --help
174     Turbo C# compiler, Copyright 2001-2011 Novell, Inc., 2011-2016 Xamarin, Inc, 2016-2017 Microsoft Corp
175     mcs [options] source-files
176        --about              About the Mono C# compiler
177        -addmodule:M1[,Mn]   Adds the module to the generated assembly
178        -checked[+|-]        Sets default aritmetic overflow context
179        -clscheck[+|-]       Disables CLS Compliance verifications
180        -codepage:ID         Sets code page to the one in ID (number, utf8, reset)
181        -define:S1[;S2]      Defines one or more conditional symbols (short: -d)
182        -debug[+|-], -g      Generate debugging information
183        -delaysign[+|-]      Only insert the public key into the assembly (no signing)
184        -doc:FILE            Process documentation comments to XML file
185        -fullpaths           Any issued error or warning uses absolute file path
186        -help                Lists all compiler options (short: -?)
187        -keycontainer:NAME   The key pair container used to sign the output assembly
188        -keyfile:FILE        The key file used to strongname the ouput assembly
189        -langversion:TEXT    Specifies language version: ISO-1, ISO-2, 3, 4, 5, 6, Default or Experimental
190        -lib:PATH1[,PATHn]   Specifies the location of referenced assemblies
191        -main:CLASS          Specifies the class with the Main method (short: -m)
192        -noconfig            Disables implicitly referenced assemblies
193        -nostdlib[+|-]       Does not reference mscorlib.dll library
194        -nowarn:W1[,Wn]      Suppress one or more compiler warnings
195        -optimize[+|-]       Enables advanced compiler optimizations (short: -o)
196        -out:FILE            Specifies output assembly name
197        -pathmap:K=V[,Kn=Vn] Sets a mapping for source path names used in generated output
198        -pkg:P1[,Pn]         References packages P1..Pn
199        -platform:ARCH       Specifies the target platform of the output assembly
200                             ARCH can be one of: anycpu, anycpu32bitpreferred, arm,
201                             x86, x64 or itanium. The default is anycpu.
202        -recurse:SPEC        Recursively compiles files according to SPEC pattern
203        -reference:A1[,An]   Imports metadata from the specified assembly (short: -r)
204        -reference:ALIAS=A   Imports metadata using specified extern alias (short: -r)
205        -sdk:VERSION         Specifies SDK version of referenced assemblies
206                             VERSION can be one of: 2, 4, 4.5 (default) or a custom value
207        -target:KIND         Specifies the format of the output assembly (short: -t)
208                             KIND can be one of: exe, winexe, library, module
209        -unsafe[+|-]         Allows to compile code which uses unsafe keyword
210        -warnaserror[+|-]    Treats all warnings as errors
211        -warnaserror[+|-]:W1[,Wn] Treats one or more compiler warnings as errors
212        -warn:0-4            Sets warning level, the default is 4 (short -w:)
213        -helpinternal        Shows internal and advanced compiler options
214 
215     Resources:
216        -linkresource:FILE[,ID] Links FILE as a resource (short: -linkres)
217        -resource:FILE[,ID]     Embed FILE as a resource (short: -res)
218        -win32res:FILE          Specifies Win32 resource file (.res)
219        -win32icon:FILE         Use this icon for the output
220        @file                   Read response file for more options
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     String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles) throws CompilerException {
389         List<String> args = new ArrayList<>();
390 
391         // config.isShowWarnings()
392         // config.getSourceVersion()
393         // config.getTargetVersion()
394         // config.getSourceEncoding()
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         // TODO: include all user compiler arguments and not only some!
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         // Xml Doc output
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         // Debug option (full, pdbonly...)
441         String debug = compilerArguments.get("-debug");
442         if (!StringUtils.isEmpty(debug)) {
443             args.add("/debug:" + debug);
444         }
445 
446         // Nowarn option (w#1,w#2...)
447         String nowarn = compilerArguments.get("-nowarn");
448         if (!StringUtils.isEmpty(nowarn)) {
449             args.add("/nowarn:" + nowarn);
450         }
451 
452         // Out - Override output name, this is required for generating the unit test dll
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         // Resource File - compile in a resource file into the assembly being created
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         // Target - type of assembly to produce: library,exe,winexe...
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         // remove MS logo from output (not applicable for mono)
476         String nologo = compilerArguments.get("-nologo");
477         if (!StringUtils.isEmpty(nologo) && !"false".equalsIgnoreCase(nologo)) {
478             args.add("/nologo");
479         }
480 
481         // Unsafe option
482         String unsafe = compilerArguments.get("-unsafe");
483         if (!StringUtils.isEmpty(unsafe) && "true".equalsIgnoreCase(unsafe)) {
484             args.add("/unsafe");
485         }
486 
487         // PreferredUILang option
488         String preferreduilang = compilerArguments.get("-preferreduilang");
489         if (!StringUtils.isEmpty(preferreduilang)) {
490             args.add("/preferreduilang:" + preferreduilang);
491         }
492 
493         // Utf8Output option
494         String utf8output = compilerArguments.get("-utf8output");
495         if (!StringUtils.isEmpty(utf8output) && !"false".equals(utf8output)) {
496             args.add("/utf8output");
497         }
498 
499         // add any resource files
500         this.addResourceArgs(config, args);
501 
502         // add source files
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         // Build the @arguments file
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         // Execute!
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             // TODO: exception?
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     // added for debug purposes....
668     protected static String[] getSourceFiles(CompilerConfiguration config) {
669         Set<String> sources = new HashSet<>();
670 
671         // Set sourceFiles = null;
672         // was:
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 }