Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / System / Microsoft.VisualBasic / VBCodeCompiler.cs
1 //
2 // Microsoft VisualBasic VBCodeCompiler Class implementation
3 //
4 // Authors:
5 //      Jochen Wezel (jwezel@compumaster.de)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2003 Jochen Wezel (http://www.compumaster.de)
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
10 //
11 // Modifications:
12 // 2003-11-28 JW: create reference to Microsoft.VisualBasic if not explicitly done
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System;
36 using System.CodeDom;
37 using System.CodeDom.Compiler;
38 using System.ComponentModel;
39 using System.IO;
40 using System.Text;
41 using System.Reflection;
42 using System.Collections;
43 using System.Collections.Specialized;
44 using System.Diagnostics;
45 using System.Text.RegularExpressions;
46
47 namespace Microsoft.VisualBasic
48 {
49         internal class VBCodeCompiler : VBCodeGenerator, ICodeCompiler
50         {
51                 static string windowsMonoPath;
52                 static string windowsvbncPath;
53
54                 static VBCodeCompiler ()
55                 {
56                         if (Path.DirectorySeparatorChar == '\\') {
57                                 PropertyInfo gac = typeof (Environment).GetProperty ("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
58                                 MethodInfo get_gac = gac.GetGetMethod (true);
59                                 string p = Path.GetDirectoryName (
60                                         (string) get_gac.Invoke (null, null));
61                                 windowsMonoPath = Path.Combine (
62                                         Path.GetDirectoryName (
63                                                 Path.GetDirectoryName (p)),
64                                         "bin\\mono.bat");
65                                 if (!File.Exists (windowsMonoPath))
66                                         windowsMonoPath = Path.Combine (
67                                                 Path.GetDirectoryName (
68                                                         Path.GetDirectoryName (p)),
69                                                 "bin\\mono.exe");
70                                 windowsvbncPath =
71                                         Path.Combine (p, "2.0\\vbnc.exe");
72                         }
73                 }
74
75                 public CompilerResults CompileAssemblyFromDom (CompilerParameters options, CodeCompileUnit e)
76                 {
77                         return CompileAssemblyFromDomBatch (options, new CodeCompileUnit[] { e });
78                 }
79
80                 public CompilerResults CompileAssemblyFromDomBatch (CompilerParameters options, CodeCompileUnit[] ea)
81                 {
82                         if (options == null) {
83                                 throw new ArgumentNullException ("options");
84                         }
85
86                         try {
87                                 return CompileFromDomBatch (options, ea);
88                         } finally {
89                                 options.TempFiles.Delete ();
90                         }
91                 }
92
93                 public CompilerResults CompileAssemblyFromFile (CompilerParameters options, string fileName)
94                 {
95                         return CompileAssemblyFromFileBatch (options, new string[] { fileName });
96                 }
97
98                 public CompilerResults CompileAssemblyFromFileBatch (CompilerParameters options, string[] fileNames)
99                 {
100                         if (options == null) {
101                                 throw new ArgumentNullException ("options");
102                         }
103
104                         try {
105                                 return CompileFromFileBatch (options, fileNames);
106                         } finally {
107                                 options.TempFiles.Delete ();
108                         }
109                 }
110
111                 public CompilerResults CompileAssemblyFromSource (CompilerParameters options, string source)
112                 {
113                         return CompileAssemblyFromSourceBatch (options, new string[] { source });
114                 }
115
116                 public CompilerResults CompileAssemblyFromSourceBatch (CompilerParameters options, string[] sources)
117                 {
118                         if (options == null) {
119                                 throw new ArgumentNullException ("options");
120                         }
121
122                         try {
123                                 return CompileFromSourceBatch (options, sources);
124                         } finally {
125                                 options.TempFiles.Delete ();
126                         }
127                 }
128
129                 static string BuildArgs (CompilerParameters options, string[] fileNames)
130                 {
131                         StringBuilder args = new StringBuilder ();
132                         args.Append ("/quiet ");
133                         if (options.GenerateExecutable)
134                                 args.Append ("/target:exe ");
135                         else
136                                 args.Append ("/target:library ");
137
138                         /* Disabled. It causes problems now. -- Gonzalo
139                         if (options.IncludeDebugInformation)
140                                 args.AppendFormat("/debug ");
141                         */
142
143                         if (options.TreatWarningsAsErrors)
144                                 args.Append ("/warnaserror ");
145
146                         /* Disabled. vbnc does not support warninglevels.
147                         if (options.WarningLevel != -1)
148                                 args.AppendFormat ("/wlevel:{0} ", options.WarningLevel);
149                         */
150
151                         if (options.OutputAssembly == null || options.OutputAssembly.Length == 0) {
152                                 string ext = (options.GenerateExecutable ? "exe" : "dll");
153                                 options.OutputAssembly = GetTempFileNameWithExtension (options.TempFiles, ext, !options.GenerateInMemory);
154                         }
155
156                         args.AppendFormat ("/out:\"{0}\" ", options.OutputAssembly);
157
158                         bool Reference2MSVBFound;
159                         Reference2MSVBFound = false;
160                         if (null != options.ReferencedAssemblies) {
161                                 foreach (string import in options.ReferencedAssemblies) {
162                                         if (string.Compare (import, "Microsoft.VisualBasic", true, System.Globalization.CultureInfo.InvariantCulture) == 0)
163                                                 Reference2MSVBFound = true;
164                                         args.AppendFormat ("/r:\"{0}\" ", import);
165                                 }
166                         }
167                         
168                         // add standard import to Microsoft.VisualBasic if missing
169                         if (!Reference2MSVBFound)
170                                 args.Append ("/r:\"Microsoft.VisualBasic.dll\" ");
171
172                         if (options.CompilerOptions != null) {
173                                 args.Append (options.CompilerOptions);
174                                 args.Append (" ");
175                         }
176                         /* Disabled, vbnc does not support this.
177                         args.Append (" -- "); // makes vbnc not try to process filenames as options
178                         */
179                         foreach (string source in fileNames)
180                                 args.AppendFormat (" \"{0}\" ", source);
181
182                         return args.ToString ();
183                 }
184
185                 static CompilerError CreateErrorFromString (string error_string)
186                 {
187                         CompilerError error = new CompilerError ();
188                         Regex reg = new Regex (@"^(\s*(?<file>.*)?\((?<line>\d*)(,(?<column>\d*))?\)\s+)?:\s*" +
189                                                 @"(?<level>Error|Warning)?\s*(?<number>.*):\s(?<message>.*)",
190                                                 RegexOptions.Compiled | RegexOptions.ExplicitCapture);
191
192                         Match match = reg.Match (error_string);
193                         if (!match.Success) {
194                                 return null;
195                         }
196
197                         if (String.Empty != match.Result ("${file}"))
198                                 error.FileName = match.Result ("${file}").Trim ();
199
200                         if (String.Empty != match.Result ("${line}"))
201                                 error.Line = Int32.Parse (match.Result ("${line}"));
202
203                         if (String.Empty != match.Result ("${column}"))
204                                 error.Column = Int32.Parse (match.Result ("${column}"));
205
206                         if (match.Result ("${level}").Trim () == "Warning")
207                                 error.IsWarning = true;
208
209                         error.ErrorNumber = match.Result ("${number}");
210                         error.ErrorText = match.Result ("${message}");
211                         
212                         return error;
213                 }
214
215                 private static string GetTempFileNameWithExtension (TempFileCollection temp_files, string extension, bool keepFile)
216                 {
217                         return temp_files.AddExtension (extension, keepFile);
218                 }
219
220                 private static string GetTempFileNameWithExtension (TempFileCollection temp_files, string extension)
221                 {
222                         return temp_files.AddExtension (extension);
223                 }
224
225                 private CompilerResults CompileFromFileBatch (CompilerParameters options, string[] fileNames)
226                 {
227                         
228                         if (options == null) {
229                                 throw new ArgumentNullException ("options");
230                         }
231
232                         if (fileNames == null) {
233                                 throw new ArgumentNullException ("fileNames");
234                         }
235
236                         CompilerResults results = new CompilerResults (options.TempFiles);
237                         Process vbnc = new Process ();
238
239                         string vbnc_output = "";
240                         string[] vbnc_output_lines;
241                         // FIXME: these lines had better be platform independent.
242                         if (Path.DirectorySeparatorChar == '\\') {
243                                 vbnc.StartInfo.FileName = windowsMonoPath;
244                                 vbnc.StartInfo.Arguments = windowsvbncPath + ' ' + BuildArgs (options, fileNames);
245                         } else {
246                                 vbnc.StartInfo.FileName = "vbnc";
247                                 vbnc.StartInfo.Arguments = BuildArgs (options, fileNames);
248                         }
249                         //Console.WriteLine (vbnc.StartInfo.Arguments);
250                         vbnc.StartInfo.CreateNoWindow = true;
251                         vbnc.StartInfo.UseShellExecute = false;
252                         vbnc.StartInfo.RedirectStandardOutput = true;
253                         try {
254                                 vbnc.Start ();
255                         } catch (Exception e) {
256                                 Win32Exception exc = e as Win32Exception;
257                                 if (exc != null) {
258                                         throw new SystemException (String.Format ("Error running {0}: {1}", vbnc.StartInfo.FileName,
259                                                                                         Win32Exception.W32ErrorMessage (exc.NativeErrorCode)));
260                                 }
261                                 throw;
262                         }
263
264                         try {
265                                 vbnc_output = vbnc.StandardOutput.ReadToEnd ();
266                                 vbnc.WaitForExit ();
267                         } finally {
268                                 results.NativeCompilerReturnValue = vbnc.ExitCode;
269                                 vbnc.Close ();
270                         }
271
272                         bool loadIt = true;
273                         if (results.NativeCompilerReturnValue == 1) {
274                                 loadIt = false;
275                                 vbnc_output_lines = vbnc_output.Split (Environment.NewLine.ToCharArray ());
276                                 foreach (string error_line in vbnc_output_lines) {
277                                         CompilerError error = CreateErrorFromString (error_line);
278                                         if (null != error) {
279                                                 results.Errors.Add (error);
280                                         }
281                                 }
282                         }
283                         
284                         if ((loadIt == false && !results.Errors.HasErrors) // Failed, but no errors? Probably couldn't parse the compiler output correctly. 
285                             || (results.NativeCompilerReturnValue != 0 && results.NativeCompilerReturnValue != 1)) // Neither success (0), nor failure (1), so it crashed. 
286                         {
287                                 // Show the entire output as one big error message.
288                                 loadIt = false;
289                                 CompilerError error = new CompilerError (string.Empty, 0, 0, "VBNC_CRASH", vbnc_output);
290                                 results.Errors.Add (error);
291                         };
292
293                         if (loadIt) {
294                                 if (options.GenerateInMemory) {
295                                         using (FileStream fs = File.OpenRead (options.OutputAssembly)) {
296                                                 byte[] buffer = new byte[fs.Length];
297                                                 fs.Read (buffer, 0, buffer.Length);
298                                                 results.CompiledAssembly = Assembly.Load (buffer, null);
299                                                 fs.Close ();
300                                         }
301                                 } else {
302                                         results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
303                                         results.PathToAssembly = options.OutputAssembly;
304                                 }
305                         } else {
306                                 results.CompiledAssembly = null;
307                         }
308
309                         return results;
310                 }
311
312                 private CompilerResults CompileFromDomBatch (CompilerParameters options, CodeCompileUnit[] ea)
313                 {
314                         if (options == null) {
315                                 throw new ArgumentNullException ("options");
316                         }
317
318                         if (ea == null) {
319                                 throw new ArgumentNullException ("ea");
320                         }
321
322                         string[] fileNames = new string[ea.Length];
323                         StringCollection assemblies = options.ReferencedAssemblies;
324
325                         for (int i = 0; i < ea.Length; i++) {
326                                 CodeCompileUnit compileUnit = ea[i];
327                                 fileNames[i] = GetTempFileNameWithExtension (options.TempFiles, i + ".vb");
328                                 FileStream f = new FileStream (fileNames[i], FileMode.OpenOrCreate);
329                                 StreamWriter s = new StreamWriter (f);
330                                 if (compileUnit.ReferencedAssemblies != null) {
331                                         foreach (string str in compileUnit.ReferencedAssemblies) {
332                                                 if (!assemblies.Contains (str))
333                                                         assemblies.Add (str);
334                                         }
335                                 }
336
337                                 ((ICodeGenerator) this).GenerateCodeFromCompileUnit (compileUnit, s, new CodeGeneratorOptions ());
338                                 s.Close ();
339                                 f.Close ();
340                         }
341                         return CompileAssemblyFromFileBatch (options, fileNames);
342                 }
343
344                 private CompilerResults CompileFromSourceBatch (CompilerParameters options, string[] sources)
345                 {
346                         if (options == null) {
347                                 throw new ArgumentNullException ("options");
348                         }
349
350                         if (sources == null) {
351                                 throw new ArgumentNullException ("sources");
352                         }
353
354                         string[] fileNames = new string[sources.Length];
355
356                         for (int i = 0; i < sources.Length; i++) {
357                                 fileNames[i] = GetTempFileNameWithExtension (options.TempFiles, i + ".vb");
358                                 FileStream f = new FileStream (fileNames[i], FileMode.OpenOrCreate);
359                                 using (StreamWriter s = new StreamWriter (f)) {
360                                         s.Write (sources[i]);
361                                         s.Close ();
362                                 }
363                                 f.Close ();
364                         }
365                         return CompileFromFileBatch (options, fileNames);
366                 }
367         }
368 }
369