2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System / Microsoft.CSharp / CSharpCodeCompiler.cs
blobe87382edba56daf6811841516503fd4ae82d4319
1 //
2 // Mono.CSharp CSharpCodeCompiler Class implementation
3 //
4 // Authors:
5 // Sean Kasun (seank@users.sf.net)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // Copyright (c) Novell, Inc. (http://www.novell.com)
9 //
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:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
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.
32 namespace Mono.CSharp
34 using System;
35 using System.CodeDom;
36 using System.CodeDom.Compiler;
37 using System.ComponentModel;
38 using System.IO;
39 using System.Text;
40 using System.Reflection;
41 using System.Collections;
42 using System.Collections.Specialized;
43 using System.Diagnostics;
44 using System.Text.RegularExpressions;
46 #if NET_2_0
47 using System.Threading;
48 using System.Collections.Generic;
49 #endif
51 internal class CSharpCodeCompiler : CSharpCodeGenerator, ICodeCompiler
53 static string windowsMcsPath;
54 static string windowsMonoPath;
56 #if NET_2_0
57 Mutex mcsOutMutex;
58 StringCollection mcsOutput;
59 #endif
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)),
71 "bin\\mono.bat");
72 if (!File.Exists (windowsMonoPath))
73 windowsMonoPath = Path.Combine (
74 Path.GetDirectoryName (
75 Path.GetDirectoryName (p)),
76 "bin\\mono.exe");
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);
85 #if NET_4_0
86 windowsMcsPath =
87 Path.Combine (p, "4.0\\dmcs.exe");
88 #elif NET_2_0
89 windowsMcsPath =
90 Path.Combine (p, "2.0\\gmcs.exe");
91 #else
92 windowsMcsPath =
93 Path.Combine (p, "1.0\\mcs.exe");
94 #endif
95 if (!File.Exists (windowsMcsPath))
96 #if NET_4_0
97 windowsMcsPath =
98 Path.Combine(
99 Path.GetDirectoryName (p),
100 "lib\\net_4_0\\dmcs.exe");
101 #elif NET_2_0
102 windowsMcsPath =
103 Path.Combine(
104 Path.GetDirectoryName (p),
105 "lib\\net_2_0\\gmcs.exe");
106 #else
107 windowsMcsPath =
108 Path.Combine(
109 Path.GetDirectoryName (p),
110 "lib\\default\\mcs.exe");
111 #endif
112 if (!File.Exists (windowsMcsPath))
113 throw new FileNotFoundException ("Windows mcs path not found: " + windowsMcsPath);
118 // Constructors
120 public CSharpCodeCompiler()
124 #if NET_2_0
125 public CSharpCodeCompiler (IDictionary <string, string> providerOptions) :
126 base (providerOptions)
129 #endif
132 // Methods
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");
145 try {
146 return CompileFromDomBatch (options, ea);
147 } finally {
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");
163 try {
164 return CompileFromFileBatch (options, fileNames);
165 } finally {
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");
181 try {
182 return CompileFromSourceBatch (options, sources);
183 } finally {
184 options.TempFiles.Delete ();
188 private CompilerResults CompileFromFileBatch (CompilerParameters options, string[] fileNames)
190 if (null == options)
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();
198 #if !NET_2_0
199 string mcs_output;
200 string mcs_stdout;
201 string[] mcsOutput;
202 #endif
204 // FIXME: these lines had better be platform independent.
205 if (Path.DirectorySeparatorChar == '\\') {
206 mcs.StartInfo.FileName = windowsMonoPath;
207 mcs.StartInfo.Arguments = "\"" + windowsMcsPath + "\" " +
208 #if NET_2_0
209 BuildArgs (options, fileNames, ProviderOptions);
210 #else
211 BuildArgs (options, fileNames);
212 #endif
213 } else {
214 #if NET_2_0
215 // FIXME: This is a temporary hack to make code genaration work in 2.0+
216 #if NET_4_0
217 mcs.StartInfo.FileName="dmcs";
218 #else
219 mcs.StartInfo.FileName="gmcs";
220 #endif
221 mcs.StartInfo.Arguments=BuildArgs(options, fileNames, ProviderOptions);
222 #else
223 mcs.StartInfo.FileName="mcs";
224 mcs.StartInfo.Arguments=BuildArgs(options, fileNames);
225 #endif
228 #if NET_2_0
229 mcsOutput = new StringCollection ();
230 mcsOutMutex = new Mutex ();
231 #endif
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;
245 else
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;
253 #if NET_2_0
254 mcs.ErrorDataReceived += new DataReceivedEventHandler (McsStderrDataReceived);
255 #endif
257 try {
258 mcs.Start();
259 } catch (Exception e) {
260 Win32Exception exc = e as Win32Exception;
261 if (exc != null) {
262 throw new SystemException (String.Format ("Error running {0}: {1}", mcs.StartInfo.FileName,
263 Win32Exception.W32ErrorMessage (exc.NativeErrorCode)));
265 throw;
268 try {
269 #if NET_2_0
270 mcs.BeginOutputReadLine ();
271 mcs.BeginErrorReadLine ();
272 #else
273 // If there are a few kB in stdout, we might lock
274 mcs_output=mcs.StandardError.ReadToEnd();
275 mcs_stdout=mcs.StandardOutput.ReadToEnd ();
276 #endif
277 mcs.WaitForExit();
279 results.NativeCompilerReturnValue = mcs.ExitCode;
280 } finally {
281 #if NET_2_0
282 mcs.CancelErrorRead ();
283 mcs.CancelOutputRead ();
284 #endif
286 mcs.Close();
289 #if NET_2_0
290 StringCollection sc = mcsOutput;
291 #else
292 mcsOutput = mcs_output.Split (System.Environment.NewLine.ToCharArray ());
293 StringCollection sc = new StringCollection ();
294 #endif
296 bool loadIt=true;
297 foreach (string error_line in mcsOutput) {
298 #if !NET_2_0
299 sc.Add (error_line);
300 #endif
301 CompilerError error = CreateErrorFromString (error_line);
302 if (error != null) {
303 results.Errors.Add (error);
304 if (!error.IsWarning)
305 loadIt = false;
309 if (sc.Count > 0) {
310 sc.Insert (0, mcs.StartInfo.FileName + " " + mcs.StartInfo.Arguments + Environment.NewLine);
311 results.Output = sc;
314 if (loadIt) {
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);
328 fs.Close();
330 } else {
331 // Avoid setting CompiledAssembly right now since the output might be a netmodule
332 results.PathToAssembly = options.OutputAssembly;
334 } else {
335 results.CompiledAssembly = null;
338 return results;
341 #if NET_2_0
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)
352 #else
353 private static string BuildArgs(CompilerParameters options,string[] fileNames)
354 #endif
356 StringBuilder args=new StringBuilder();
357 if (options.GenerateExecutable)
358 args.Append("/target:exe ");
359 else
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- ");
372 else
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)
390 continue;
392 args.AppendFormat("/r:\"{0}\" ",import);
395 if (options.CompilerOptions != null) {
396 args.Append (options.CompilerOptions);
397 args.Append (" ");
400 #if NET_2_0
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) {
410 string langver;
412 if (!providerOptions.TryGetValue ("CompilerVersion", out langver))
413 #if NET_4_0
414 langver = "3.5";
415 #else
416 langver = "2.0";
417 #endif
419 if (langver.Length >= 1 && langver [0] == 'v')
420 langver = langver.Substring (1);
422 switch (langver) {
423 case "2.0":
424 args.Append ("/langversion:ISO-2");
425 break;
427 case "3.5":
428 // current default, omit the switch
429 break;
432 #endif
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)
441 #if NET_2_0
442 if (error_string.StartsWith ("BETA"))
443 return null;
444 #endif
445 if (error_string == null || error_string == "")
446 return null;
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 = "";
457 return error;
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}");
474 return error;
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");
493 if (ea == null) {
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 ());
513 s.Close ();
514 f.Close ();
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]);
536 s.Close ();
538 f.Close ();
540 return CompileFromFileBatch (options, fileNames);