2 // Mono.CSharp CSharpCodeCompiler Class implementation
5 // Sean Kasun (seank@users.sf.net)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // Copyright (c) Novell, Inc. (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System
.CodeDom
.Compiler
;
37 using System
.ComponentModel
;
40 using System
.Reflection
;
41 using System
.Collections
;
42 using System
.Collections
.Specialized
;
43 using System
.Diagnostics
;
44 using System
.Text
.RegularExpressions
;
47 using System
.Threading
;
48 using System
.Collections
.Generic
;
51 internal class CSharpCodeCompiler
: CSharpCodeGenerator
, ICodeCompiler
53 static string windowsMcsPath
;
54 static string windowsMonoPath
;
58 StringCollection mcsOutput
;
61 static CSharpCodeCompiler ()
63 if (Path
.DirectorySeparatorChar
== '\\') {
64 PropertyInfo gac
= typeof (Environment
).GetProperty ("GacPath", BindingFlags
.Static
|BindingFlags
.NonPublic
);
65 MethodInfo get_gac
= gac
.GetGetMethod (true);
66 string p
= Path
.GetDirectoryName (
67 (string) get_gac
.Invoke (null, null));
68 windowsMonoPath
= Path
.Combine (
69 Path
.GetDirectoryName (
70 Path
.GetDirectoryName (p
)),
72 if (!File
.Exists (windowsMonoPath
))
73 windowsMonoPath
= Path
.Combine (
74 Path
.GetDirectoryName (
75 Path
.GetDirectoryName (p
)),
77 if (!File
.Exists (windowsMonoPath
))
78 windowsMonoPath
= Path
.Combine (
79 Path
.GetDirectoryName (
80 Path
.GetDirectoryName (
81 Path
.GetDirectoryName (p
))),
82 "mono\\mono\\mini\\mono.exe");
83 if (!File
.Exists (windowsMonoPath
))
84 throw new FileNotFoundException ("Windows mono path not found: " + windowsMonoPath
);
87 Path
.Combine (p
, "4.0\\dmcs.exe");
90 Path
.Combine (p
, "2.0\\gmcs.exe");
93 Path
.Combine (p
, "1.0\\mcs.exe");
95 if (!File
.Exists (windowsMcsPath
))
99 Path
.GetDirectoryName (p
),
100 "lib\\net_4_0\\dmcs.exe");
104 Path
.GetDirectoryName (p
),
105 "lib\\net_2_0\\gmcs.exe");
109 Path
.GetDirectoryName (p
),
110 "lib\\default\\mcs.exe");
112 if (!File
.Exists (windowsMcsPath
))
113 throw new FileNotFoundException ("Windows mcs path not found: " + windowsMcsPath
);
120 public CSharpCodeCompiler()
125 public CSharpCodeCompiler (IDictionary
<string, string> providerOptions
) :
126 base (providerOptions
)
134 public CompilerResults
CompileAssemblyFromDom (CompilerParameters options
, CodeCompileUnit e
)
136 return CompileAssemblyFromDomBatch (options
, new CodeCompileUnit
[] { e }
);
139 public CompilerResults
CompileAssemblyFromDomBatch (CompilerParameters options
, CodeCompileUnit
[] ea
)
141 if (options
== null) {
142 throw new ArgumentNullException ("options");
146 return CompileFromDomBatch (options
, ea
);
148 options
.TempFiles
.Delete ();
152 public CompilerResults
CompileAssemblyFromFile (CompilerParameters options
, string fileName
)
154 return CompileAssemblyFromFileBatch (options
, new string[] { fileName }
);
157 public CompilerResults
CompileAssemblyFromFileBatch (CompilerParameters options
, string[] fileNames
)
159 if (options
== null) {
160 throw new ArgumentNullException ("options");
164 return CompileFromFileBatch (options
, fileNames
);
166 options
.TempFiles
.Delete ();
170 public CompilerResults
CompileAssemblyFromSource (CompilerParameters options
, string source
)
172 return CompileAssemblyFromSourceBatch (options
, new string[] { source }
);
175 public CompilerResults
CompileAssemblyFromSourceBatch (CompilerParameters options
, string[] sources
)
177 if (options
== null) {
178 throw new ArgumentNullException ("options");
182 return CompileFromSourceBatch (options
, sources
);
184 options
.TempFiles
.Delete ();
188 private CompilerResults
CompileFromFileBatch (CompilerParameters options
, string[] fileNames
)
191 throw new ArgumentNullException("options");
192 if (null == fileNames
)
193 throw new ArgumentNullException("fileNames");
195 CompilerResults results
=new CompilerResults(options
.TempFiles
);
196 Process mcs
=new Process();
204 // FIXME: these lines had better be platform independent.
205 if (Path
.DirectorySeparatorChar
== '\\') {
206 mcs
.StartInfo
.FileName
= windowsMonoPath
;
207 mcs
.StartInfo
.Arguments
= "\"" + windowsMcsPath
+ "\" " +
209 BuildArgs (options
, fileNames
, ProviderOptions
);
211 BuildArgs (options
, fileNames
);
215 // FIXME: This is a temporary hack to make code genaration work in 2.0+
217 mcs
.StartInfo
.FileName
="dmcs";
219 mcs
.StartInfo
.FileName
="gmcs";
221 mcs
.StartInfo
.Arguments
=BuildArgs(options
, fileNames
, ProviderOptions
);
223 mcs
.StartInfo
.FileName
="mcs";
224 mcs
.StartInfo
.Arguments
=BuildArgs(options
, fileNames
);
229 mcsOutput
= new StringCollection ();
230 mcsOutMutex
= new Mutex ();
233 string monoPath
= Environment
.GetEnvironmentVariable ("MONO_PATH");
234 if (monoPath
== null)
235 monoPath
= String
.Empty
;
237 string privateBinPath
= AppDomain
.CurrentDomain
.SetupInformation
.PrivateBinPath
;
238 if (privateBinPath
!= null && privateBinPath
.Length
> 0)
239 monoPath
= String
.Format ("{0}:{1}", privateBinPath
, monoPath
);
241 if (monoPath
.Length
> 0) {
242 StringDictionary dict
= mcs
.StartInfo
.EnvironmentVariables
;
243 if (dict
.ContainsKey ("MONO_PATH"))
244 dict
["MONO_PATH"] = monoPath
;
246 dict
.Add ("MONO_PATH", monoPath
);
249 mcs
.StartInfo
.CreateNoWindow
=true;
250 mcs
.StartInfo
.UseShellExecute
=false;
251 mcs
.StartInfo
.RedirectStandardOutput
=true;
252 mcs
.StartInfo
.RedirectStandardError
=true;
254 mcs
.ErrorDataReceived
+= new DataReceivedEventHandler (McsStderrDataReceived
);
259 } catch (Exception e
) {
260 Win32Exception exc
= e
as Win32Exception
;
262 throw new SystemException (String
.Format ("Error running {0}: {1}", mcs
.StartInfo
.FileName
,
263 Win32Exception
.W32ErrorMessage (exc
.NativeErrorCode
)));
270 mcs
.BeginOutputReadLine ();
271 mcs
.BeginErrorReadLine ();
273 // If there are a few kB in stdout, we might lock
274 mcs_output
=mcs
.StandardError
.ReadToEnd();
275 mcs_stdout
=mcs
.StandardOutput
.ReadToEnd ();
279 results
.NativeCompilerReturnValue
= mcs
.ExitCode
;
282 mcs
.CancelErrorRead ();
283 mcs
.CancelOutputRead ();
290 StringCollection sc
= mcsOutput
;
292 mcsOutput
= mcs_output
.Split (System
.Environment
.NewLine
.ToCharArray ());
293 StringCollection sc
= new StringCollection ();
297 foreach (string error_line
in mcsOutput
) {
301 CompilerError error
= CreateErrorFromString (error_line
);
303 results
.Errors
.Add (error
);
304 if (!error
.IsWarning
)
310 sc
.Insert (0, mcs
.StartInfo
.FileName
+ " " + mcs
.StartInfo
.Arguments
+ Environment
.NewLine
);
315 if (!File
.Exists (options
.OutputAssembly
)) {
316 StringBuilder sb
= new StringBuilder ();
317 foreach (string s
in sc
)
318 sb
.Append (s
+ Environment
.NewLine
);
320 throw new Exception ("Compiler failed to produce the assembly. Output: '" + sb
.ToString () + "'");
323 if (options
.GenerateInMemory
) {
324 using (FileStream fs
= File
.OpenRead(options
.OutputAssembly
)) {
325 byte[] buffer
= new byte[fs
.Length
];
326 fs
.Read(buffer
, 0, buffer
.Length
);
327 results
.CompiledAssembly
= Assembly
.Load(buffer
, null, options
.Evidence
);
331 // Avoid setting CompiledAssembly right now since the output might be a netmodule
332 results
.PathToAssembly
= options
.OutputAssembly
;
335 results
.CompiledAssembly
= null;
342 void McsStderrDataReceived (object sender
, DataReceivedEventArgs args
)
344 if (args
.Data
!= null) {
345 mcsOutMutex
.WaitOne ();
346 mcsOutput
.Add (args
.Data
);
347 mcsOutMutex
.ReleaseMutex ();
351 private static string BuildArgs(CompilerParameters options
,string[] fileNames
, IDictionary
<string, string> providerOptions
)
353 private static string BuildArgs(CompilerParameters options
,string[] fileNames
)
356 StringBuilder args
=new StringBuilder();
357 if (options
.GenerateExecutable
)
358 args
.Append("/target:exe ");
360 args
.Append("/target:library ");
362 string privateBinPath
= AppDomain
.CurrentDomain
.SetupInformation
.PrivateBinPath
;
363 if (privateBinPath
!= null && privateBinPath
.Length
> 0)
364 args
.AppendFormat ("/lib:\"{0}\" ", privateBinPath
);
366 if (options
.Win32Resource
!= null)
367 args
.AppendFormat("/win32res:\"{0}\" ",
368 options
.Win32Resource
);
370 if (options
.IncludeDebugInformation
)
371 args
.Append("/debug+ /optimize- ");
373 args
.Append("/debug- /optimize+ ");
375 if (options
.TreatWarningsAsErrors
)
376 args
.Append("/warnaserror ");
378 if (options
.WarningLevel
>= 0)
379 args
.AppendFormat ("/warn:{0} ", options
.WarningLevel
);
381 if (options
.OutputAssembly
== null || options
.OutputAssembly
.Length
== 0) {
382 string extension
= (options
.GenerateExecutable
? "exe" : "dll");
383 options
.OutputAssembly
= GetTempFileNameWithExtension (options
.TempFiles
, extension
,
384 !options
.GenerateInMemory
);
386 args
.AppendFormat("/out:\"{0}\" ",options
.OutputAssembly
);
388 foreach (string import
in options
.ReferencedAssemblies
) {
389 if (import
== null || import
.Length
== 0)
392 args
.AppendFormat("/r:\"{0}\" ",import
);
395 if (options
.CompilerOptions
!= null) {
396 args
.Append (options
.CompilerOptions
);
401 foreach (string embeddedResource
in options
.EmbeddedResources
) {
402 args
.AppendFormat("/resource:\"{0}\" ", embeddedResource
);
405 foreach (string linkedResource
in options
.LinkedResources
) {
406 args
.AppendFormat("/linkresource:\"{0}\" ", linkedResource
);
409 if (providerOptions
!= null && providerOptions
.Count
> 0) {
412 if (!providerOptions
.TryGetValue ("CompilerVersion", out langver
))
419 if (langver
.Length
>= 1 && langver
[0] == 'v')
420 langver
= langver
.Substring (1);
424 args
.Append ("/langversion:ISO-2");
428 // current default, omit the switch
434 args
.Append (" -- ");
435 foreach (string source
in fileNames
)
436 args
.AppendFormat("\"{0}\" ",source
);
437 return args
.ToString();
439 private static CompilerError
CreateErrorFromString(string error_string
)
442 if (error_string
.StartsWith ("BETA"))
445 if (error_string
== null || error_string
== "")
448 CompilerError error
=new CompilerError();
449 Regex reg
= new Regex (@"^(\s*(?<file>.*)\((?<line>\d*)(,(?<column>\d*))?\)(:)?\s+)*(?<level>\w+)\s*(?<number>.*):\s(?<message>.*)",
450 RegexOptions
.Compiled
| RegexOptions
.ExplicitCapture
);
451 Match match
=reg
.Match(error_string
);
452 if (!match
.Success
) {
453 // We had some sort of runtime crash
454 error
.ErrorText
= error_string
;
455 error
.IsWarning
= false;
456 error
.ErrorNumber
= "";
459 if (String
.Empty
!= match
.Result("${file}"))
460 error
.FileName
=match
.Result("${file}");
461 if (String
.Empty
!= match
.Result("${line}"))
462 error
.Line
=Int32
.Parse(match
.Result("${line}"));
463 if (String
.Empty
!= match
.Result("${column}"))
464 error
.Column
=Int32
.Parse(match
.Result("${column}"));
466 string level
= match
.Result ("${level}");
467 if (level
== "warning")
468 error
.IsWarning
= true;
469 else if (level
!= "error")
470 return null; // error CS8028 will confuse the regex.
472 error
.ErrorNumber
=match
.Result("${number}");
473 error
.ErrorText
=match
.Result("${message}");
477 private static string GetTempFileNameWithExtension (TempFileCollection temp_files
, string extension
, bool keepFile
)
479 return temp_files
.AddExtension (extension
, keepFile
);
482 private static string GetTempFileNameWithExtension (TempFileCollection temp_files
, string extension
)
484 return temp_files
.AddExtension (extension
);
487 private CompilerResults
CompileFromDomBatch (CompilerParameters options
, CodeCompileUnit
[] ea
)
489 if (options
== null) {
490 throw new ArgumentNullException ("options");
494 throw new ArgumentNullException ("ea");
497 string[] fileNames
= new string[ea
.Length
];
498 StringCollection assemblies
= options
.ReferencedAssemblies
;
500 for (int i
= 0; i
< ea
.Length
; i
++) {
501 CodeCompileUnit compileUnit
= ea
[i
];
502 fileNames
[i
] = GetTempFileNameWithExtension (options
.TempFiles
, i
+ ".cs");
503 FileStream f
= new FileStream (fileNames
[i
], FileMode
.OpenOrCreate
);
504 StreamWriter s
= new StreamWriter (f
, Encoding
.UTF8
);
505 if (compileUnit
.ReferencedAssemblies
!= null) {
506 foreach (string str
in compileUnit
.ReferencedAssemblies
) {
507 if (!assemblies
.Contains (str
))
508 assemblies
.Add (str
);
512 ((ICodeGenerator
) this).GenerateCodeFromCompileUnit (compileUnit
, s
, new CodeGeneratorOptions ());
516 return CompileAssemblyFromFileBatch (options
, fileNames
);
519 private CompilerResults
CompileFromSourceBatch (CompilerParameters options
, string[] sources
)
521 if (options
== null) {
522 throw new ArgumentNullException ("options");
525 if (sources
== null) {
526 throw new ArgumentNullException ("sources");
529 string[] fileNames
= new string[sources
.Length
];
531 for (int i
= 0; i
< sources
.Length
; i
++) {
532 fileNames
[i
] = GetTempFileNameWithExtension (options
.TempFiles
, i
+ ".cs");
533 FileStream f
= new FileStream (fileNames
[i
], FileMode
.OpenOrCreate
);
534 using (StreamWriter s
= new StreamWriter (f
, Encoding
.UTF8
)) {
535 s
.Write (sources
[i
]);
540 return CompileFromFileBatch (options
, fileNames
);