Wed Feb 24 15:47:16 CET 2010 Paolo Molaro <lupus@ximian.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / BuildManager.cs
1 //
2 // System.Web.Compilation.BuildManager
3 //
4 // Authors:
5 //      Chris Toshok (toshok@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
7 //      Marek Habersack (mhabersack@novell.com)
8 //
9 // (C) 2006-2009 Novell, Inc (http://www.novell.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.CodeDom;
35 using System.CodeDom.Compiler;
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.Collections.Specialized;
39 using System.ComponentModel;
40 using System.IO;
41 using System.Reflection;
42 using System.Text;
43 using System.Threading;
44 using System.Xml;
45 using System.Web;
46 using System.Web.Caching;
47 using System.Web.Configuration;
48 using System.Web.Hosting;
49 using System.Web.Util;
50
51 namespace System.Web.Compilation
52 {
53         public sealed class BuildManager
54         {
55                 internal const string FAKE_VIRTUAL_PATH_PREFIX = "/@@MonoFakeVirtualPath@@";
56                 const string BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX = "@@Build_Manager@@";
57                 static int BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH = BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX.Length;
58
59                 static readonly object bigCompilationLock = new object ();
60                 static readonly char[] virtualPathsToIgnoreSplitChars = {','};
61                 
62                 static EventHandlerList events = new EventHandlerList ();
63                 static object buildManagerRemoveEntryEvent = new object ();
64                 
65                 static bool hosted;
66                 static IEqualityComparer <string> comparer;
67                 static StringComparison stringComparer;
68                 static Dictionary <string, bool> virtualPathsToIgnore;
69                 static bool haveVirtualPathsToIgnore;
70                 static List <Assembly> AppCode_Assemblies = new List<Assembly>();
71                 static List <Assembly> TopLevel_Assemblies = new List<Assembly>();
72                 static Dictionary <Type, CodeDomProvider> codeDomProviders;
73                 static Dictionary <string, BuildManagerCacheItem> buildCache;
74                 static List <Assembly> referencedAssemblies;
75
76                 static int buildCount;
77                 static bool is_precompiled;
78                 //static bool updatable; unused
79                 static Dictionary<string, PreCompilationData> precompiled;
80                 
81                 // This is here _only_ for the purpose of unit tests!
82                 internal static bool suppressDebugModeMessages;
83
84 #if SYSTEMCORE_DEP
85                 static ReaderWriterLockSlim buildCacheLock;
86 #else
87                 static ReaderWriterLock buildCacheLock;
88 #endif
89                 static ulong recursionDepth;
90
91                 internal static bool IsPrecompiled {
92                         get { return is_precompiled; }
93                 }
94                 
95                 internal static event BuildManagerRemoveEntryEventHandler RemoveEntry {
96                         add { events.AddHandler (buildManagerRemoveEntryEvent, value); }
97                         remove { events.RemoveHandler (buildManagerRemoveEntryEvent, value); }
98                 }
99                 
100                 internal static bool BatchMode {
101                         get {
102                                 if (!hosted)
103                                         return false; // Fix for bug #380985
104
105                                 CompilationSection cs = CompilationConfig;
106                                 if (cs == null)
107                                         return true;
108                                 
109                                 return cs.Batch;
110                         }
111                 }
112
113                 // Assemblies built from the App_Code directory
114                 public static IList CodeAssemblies {
115                         get { return AppCode_Assemblies; }
116                 }
117                 
118                 internal static CompilationSection CompilationConfig {
119                         get { return WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection; }
120                 }
121
122                 internal static bool HaveResources {
123                         get; set;
124                 }
125                 
126                 internal static IList TopLevelAssemblies {
127                         get { return TopLevel_Assemblies; }
128                 }
129                 
130                 static BuildManager ()
131                 {
132                         if (HttpRuntime.CaseInsensitive) {
133                                 comparer = StringComparer.CurrentCultureIgnoreCase;
134                                 stringComparer = StringComparison.CurrentCultureIgnoreCase;
135                         } else {
136                                 comparer = StringComparer.CurrentCulture;
137                                 stringComparer = StringComparison.CurrentCulture;
138                         }
139                         
140                         hosted = (AppDomain.CurrentDomain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes";
141                         buildCache = new Dictionary <string, BuildManagerCacheItem> (comparer);
142 #if SYSTEMCORE_DEP
143                         buildCacheLock = new ReaderWriterLockSlim ();
144 #else
145                         buildCacheLock = new ReaderWriterLock ();
146 #endif
147                         referencedAssemblies = new List <Assembly> ();
148                         recursionDepth = 0;
149
150                         string appPath = HttpRuntime.AppDomainAppPath;
151                         string precomp_name = null;
152                         is_precompiled = String.IsNullOrEmpty (appPath) ? false : File.Exists ((precomp_name = Path.Combine (appPath, "PrecompiledApp.config")));
153                         if (is_precompiled)
154                                 is_precompiled = LoadPrecompilationInfo (precomp_name);
155                         LoadVirtualPathsToIgnore ();
156                 }
157
158                 // Deal with precompiled sites deployed in a different virtual path
159                 static void FixVirtualPaths ()
160                 {
161                         if (precompiled == null)
162                                 return;
163                         
164                         string [] parts;
165                         int skip = -1;
166                         foreach (string vpath in precompiled.Keys) {
167                                 parts = vpath.Split ('/');
168                                 for (int i = 0; i < parts.Length; i++) {
169                                         if (String.IsNullOrEmpty (parts [i]))
170                                                 continue;
171                                         // The path must be rooted, otherwise PhysicalPath returned
172                                         // below will be relative to the current request path and
173                                         // File.Exists will return a false negative. See bug #546053
174                                         string test_path = "/" + String.Join ("/", parts, i, parts.Length - i);
175                                         VirtualPath result = GetAbsoluteVirtualPath (test_path);
176                                         if (result != null && File.Exists (result.PhysicalPath)) {
177                                                 skip = i - 1;
178                                                 break;
179                                         }
180                                 }
181                         }
182                         
183                         string app_vpath = HttpRuntime.AppDomainAppVirtualPath;
184                         if (skip == -1 || (skip == 0 && app_vpath == "/"))
185                                 return;
186
187                         if (!app_vpath.EndsWith ("/"))
188                                 app_vpath = app_vpath + "/";
189                         Dictionary<string, PreCompilationData> copy = new Dictionary<string, PreCompilationData> (precompiled);
190                         precompiled.Clear ();
191                         foreach (KeyValuePair<string,PreCompilationData> entry in copy) {
192                                 parts = entry.Key.Split ('/');
193                                 string new_path;
194                                 if (String.IsNullOrEmpty (parts [0]))
195                                         new_path = app_vpath + String.Join ("/", parts, skip + 1, parts.Length - skip - 1);
196                                 else
197                                         new_path = app_vpath + String.Join ("/", parts, skip, parts.Length - skip);
198                                 entry.Value.VirtualPath = new_path;
199                                 precompiled.Add (new_path, entry.Value);
200                         }
201                 }
202
203                 static bool LoadPrecompilationInfo (string precomp_config)
204                 {
205                         using (XmlTextReader reader = new XmlTextReader (precomp_config)) {
206                                 reader.MoveToContent ();
207                                 if (reader.Name != "precompiledApp")
208                                         return false;
209
210                                 /* unused
211                                 if (reader.HasAttributes)
212                                         while (reader.MoveToNextAttribute ())
213                                                 if (reader.Name == "updatable") {
214                                                         updatable = (reader.Value == "true");
215                                                         break;
216                                                 }
217                                 */
218                         }
219
220                         string [] compiled = Directory.GetFiles (HttpRuntime.BinDirectory, "*.compiled");
221                         foreach (string str in compiled)
222                                 LoadCompiled (str);
223
224                         FixVirtualPaths ();
225                         return true;
226                 }
227
228                 static void LoadCompiled (string filename)
229                 {
230                         using (XmlTextReader reader = new XmlTextReader (filename)) {
231                                 reader.MoveToContent ();
232                                 if (reader.Name == "preserve" && reader.HasAttributes) {
233                                         reader.MoveToNextAttribute ();
234                                         string val = reader.Value;
235                                         // 1 -> app_code subfolder - add the assembly to CodeAssemblies
236                                         // 2 -> ashx
237                                         // 3 -> ascx, aspx
238                                         // 6 -> app_code - add the assembly to CodeAssemblies
239                                         // 8 -> global.asax
240                                         // 9 -> App_GlobalResources - set the assembly for HttpContext
241                                         if (reader.Name == "resultType" && (val == "2" || val == "3" || val == "8"))
242                                                 LoadPageData (reader, true);
243                                         else if (val == "1" || val == "6") {
244                                                 PreCompilationData pd = LoadPageData (reader, false);
245                                                 CodeAssemblies.Add (Assembly.Load (pd.AssemblyFileName));
246                                         } else if (val == "9") {
247                                                 PreCompilationData pd = LoadPageData (reader, false);
248                                                 HttpContext.AppGlobalResourcesAssembly = Assembly.Load (pd.AssemblyFileName);
249                                         }
250                                 }
251                         }
252                 }
253
254                 class PreCompilationData {
255                         public string VirtualPath;
256                         public string AssemblyFileName;
257                         public string TypeName;
258                         public Type Type;
259                 }
260
261                 static PreCompilationData LoadPageData (XmlTextReader reader, bool store)
262                 {
263                         PreCompilationData pc_data = new PreCompilationData ();
264
265                         while (reader.MoveToNextAttribute ()) {
266                                 string name = reader.Name;
267                                 if (name == "virtualPath")
268                                         pc_data.VirtualPath = VirtualPathUtility.RemoveTrailingSlash (reader.Value);
269                                 else if (name == "assembly")
270                                         pc_data.AssemblyFileName = reader.Value;
271                                 else if (name == "type")
272                                         pc_data.TypeName = reader.Value;
273                         }
274                         if (store) {
275                                 if (precompiled == null)
276                                         precompiled = new Dictionary<string, PreCompilationData> (comparer);
277                                 precompiled.Add (pc_data.VirtualPath, pc_data);
278                         }
279                         return pc_data;
280                 }
281
282                 static void AddAssembly (Assembly asm, List <Assembly> al)
283                 {
284                         if (al.Contains (asm))
285                                 return;
286
287                         al.Add (asm);
288                 }
289                 
290                 static void AddPathToIgnore (string vp)
291                 {
292                         if (virtualPathsToIgnore == null)
293                                 virtualPathsToIgnore = new Dictionary <string, bool> (comparer);
294                         
295                         VirtualPath path = GetAbsoluteVirtualPath (vp);
296                         string vpAbsolute = path.Absolute;
297                         if (!virtualPathsToIgnore.ContainsKey (vpAbsolute)) {
298                                 virtualPathsToIgnore.Add (vpAbsolute, true);
299                                 haveVirtualPathsToIgnore = true;
300                         }
301                         
302                         string vpRelative = path.AppRelative;
303                         if (!virtualPathsToIgnore.ContainsKey (vpRelative)) {
304                                 virtualPathsToIgnore.Add (vpRelative, true);
305                                 haveVirtualPathsToIgnore = true;
306                         }
307
308                         if (!virtualPathsToIgnore.ContainsKey (vp)) {
309                                 virtualPathsToIgnore.Add (vp, true);
310                                 haveVirtualPathsToIgnore = true;
311                         }
312                 }
313
314                 internal static void AddToReferencedAssemblies (Assembly asm)
315                 {
316                         // should not be used
317                 }
318                 
319                 static void AssertVirtualPathExists (VirtualPath virtualPath)
320                 {
321                         string realpath;
322                         bool dothrow = false;
323                         
324                         if (virtualPath.IsFake) {
325                                 realpath = virtualPath.PhysicalPath;
326                                 if (!File.Exists (realpath) && !Directory.Exists (realpath))
327                                         dothrow = true;
328                         } else {
329                                 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
330                                 string vpAbsolute = virtualPath.Absolute;
331                                 
332                                 if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute))
333                                         dothrow = true;
334                         }
335
336                         if (dothrow)
337                                 throw new HttpException (404, "The file '" + virtualPath + "' does not exist.", virtualPath.Absolute);
338                 }
339
340                 static void Build (VirtualPath vp)
341                 {
342                         AssertVirtualPathExists (vp);
343
344                         CompilationSection cs = CompilationConfig;
345                         lock (bigCompilationLock) {
346                                 if (HasCachedItemNoLock (vp.Absolute))
347                                         return;
348
349                                 if (recursionDepth == 0)
350                                         referencedAssemblies.Clear ();
351
352                                 recursionDepth++;
353                                 try {
354                                         BuildInner (vp, cs != null ? cs.Debug : false);
355                                         if (recursionDepth <= 1)
356                                                 buildCount++;
357
358                                         // See http://support.microsoft.com/kb/319947
359                                         if (buildCount > cs.NumRecompilesBeforeAppRestart)
360                                                 HttpRuntime.UnloadAppDomain ();
361                                 } finally {
362                                         recursionDepth--;
363                                 }
364                         }
365                 }
366
367                 // This method assumes it is being called with the big compilation lock held
368                 static void BuildInner (VirtualPath vp, bool debug)
369                 {
370                         var builder = new BuildManagerDirectoryBuilder (vp);
371                         bool recursive = recursionDepth > 1;
372                         List <BuildProviderGroup> builderGroups = builder.Build (IsSingleBuild (vp, recursive));
373                         if (builderGroups == null)
374                                 return;
375
376                         string vpabsolute = vp.Absolute;
377                         int buildHash = (vpabsolute.GetHashCode () | (int)DateTime.Now.Ticks) + (int)recursionDepth;
378                         string assemblyBaseName;
379                         AssemblyBuilder abuilder;
380                         CompilerType ct;
381                         int attempts;
382                         bool singleBuild, needMainVpBuild;
383                         CompilationException compilationError;
384                         
385                         // Each group becomes a separate assembly.
386                         foreach (BuildProviderGroup group in builderGroups) {
387                                 needMainVpBuild = false;
388                                 compilationError = null;
389                                 assemblyBaseName = null;
390                                 
391                                 if (group.Count == 1) {
392                                         if (recursive || !group.Master)
393                                                 assemblyBaseName = String.Format ("{0}_{1}.{2:x}.", group.NamePrefix, VirtualPathUtility.GetFileName (group [0].VirtualPath), buildHash);
394                                         singleBuild = true;
395                                 } else
396                                         singleBuild = false;
397                                 
398                                 if (assemblyBaseName == null)
399                                         assemblyBaseName = group.NamePrefix + "_";
400                                 
401                                 ct = group.CompilerType;
402                                 attempts = 3;
403                                 while (attempts > 0) {
404                                         abuilder = new AssemblyBuilder (vp, CreateDomProvider (ct), assemblyBaseName);
405                                         abuilder.CompilerOptions = ct.CompilerParameters;
406                                         abuilder.AddAssemblyReference (GetReferencedAssemblies () as List <Assembly>);
407                                         try {
408                                                 GenerateAssembly (abuilder, group, vp, debug);
409                                                 attempts = 0;
410                                         } catch (CompilationException ex) {
411                                                 attempts--;
412                                                 if (singleBuild)
413                                                         throw new HttpException ("Single file build failed.", ex);
414                                                 
415                                                 if (attempts == 0) {
416                                                         needMainVpBuild = true;
417                                                         compilationError = ex;
418                                                         break;
419                                                 }
420                                                 
421                                                 CompilerResults results = ex.Results;
422                                                 if (results == null)
423                                                         throw new HttpException ("No results returned from failed compilation.", ex);
424                                                 else
425                                                         RemoveFailedAssemblies (vpabsolute, ex, abuilder, group, results, debug);
426                                         }
427                                 }
428
429                                 if (needMainVpBuild) {
430                                         // One last attempt - try to build just the requested path
431                                         // if it's not built yet or just return without throwing the
432                                         // exception if it has already been built. 
433                                         if (HasCachedItemNoLock (vpabsolute)) {
434                                                 if (debug)
435                                                         DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
436                                                                                   compilationError, vpabsolute);
437                                                 return;
438                                         };
439
440                                         // This will trigger a recursive build of the requested vp,
441                                         // which means only the vp alone will be built (or not); 
442                                         Build (vp);
443                                         if (HasCachedItemNoLock (vpabsolute)) {
444                                                 if (debug)
445                                                         DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
446                                                                                   compilationError, vpabsolute);
447                                                 return;
448                                         }
449
450                                         // In theory this code is unreachable. If the recursive
451                                         // build of the main vp failed, then it should have thrown
452                                         // the build exception.
453                                         throw new HttpException ("Requested virtual path build failed.", compilationError);
454                                 }
455                         }
456                 }
457                 
458                 static CodeDomProvider CreateDomProvider (CompilerType ct)
459                 {
460                         if (codeDomProviders == null)
461                                 codeDomProviders = new Dictionary <Type, CodeDomProvider> ();
462
463                         Type type = ct.CodeDomProviderType;
464                         if (type == null) {
465                                 CompilationSection cs = CompilationConfig;
466                                 CompilerType tmp = GetDefaultCompilerTypeForLanguage (cs.DefaultLanguage, cs);
467                                 if (tmp != null)
468                                         type = tmp.CodeDomProviderType;
469                         }
470
471                         if (type == null)
472                                 return null;
473                         
474                         CodeDomProvider ret;
475                         if (codeDomProviders.TryGetValue (type, out ret))
476                                 return ret;
477
478                         ret = Activator.CreateInstance (type) as CodeDomProvider;
479                         if (ret == null)
480                                 return null;
481
482                         codeDomProviders.Add (type, ret);
483                         return ret;
484                 }
485                 
486                 public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType)
487                 {
488                         return CreateInstanceFromVirtualPath (GetAbsoluteVirtualPath (virtualPath), requiredBaseType);
489                 }
490
491                 internal static object CreateInstanceFromVirtualPath (VirtualPath virtualPath, Type requiredBaseType)
492                 {
493                         if (requiredBaseType == null)
494                                 throw new NullReferenceException (); // This is what MS does, but
495                                                                      // from somewhere else.
496                         
497                         Type type = GetCompiledType (virtualPath);
498                         if (type == null)
499                                 return null;
500
501                         if (!requiredBaseType.IsAssignableFrom (type))
502                                 throw new HttpException (500,
503                                                          String.Format ("Type '{0}' does not inherit from '{1}'.", type.FullName, requiredBaseType.FullName));
504
505                         return Activator.CreateInstance (type, null);
506                 }
507                 
508                 static void DescribeCompilationError (string format, CompilationException ex, params object[] parms)
509                 {
510                         StringBuilder sb = new StringBuilder ();
511                         string newline = Environment.NewLine;
512                         
513                         if (parms != null)
514                                 sb.AppendFormat (format + newline, parms);
515                         else
516                                 sb.Append (format + newline);
517
518                         CompilerResults results = ex != null ? ex.Results : null;
519                         if (results == null)
520                                 sb.Append ("No compiler error information present." + newline);
521                         else {
522                                 sb.Append ("Compiler errors:" + newline);
523                                 foreach (CompilerError error in results.Errors)
524                                         sb.Append ("  " + error.ToString () + newline);
525                         }
526
527                         if (ex != null) {
528                                 sb.Append (newline + "Exception thrown:" + newline);
529                                 sb.Append (ex.ToString ());
530                         }
531
532                         ShowDebugModeMessage (sb.ToString ());
533                 }
534                 
535                 static BuildProvider FindBuildProviderForPhysicalPath (string path, BuildProviderGroup group, HttpRequest req)
536                 {
537                         if (req == null || String.IsNullOrEmpty (path))
538                                 return null;
539
540                         foreach (BuildProvider bp in group) {
541                                 if (String.Compare (path, req.MapPath (bp.VirtualPath), stringComparer) == 0)
542                                         return bp;
543                         }
544                         
545                         return null;
546                 }
547                 
548                 static void GenerateAssembly (AssemblyBuilder abuilder, BuildProviderGroup group, VirtualPath vp, bool debug)
549                 {
550                         IDictionary <string, bool> deps;
551                         BuildManagerCacheItem bmci;
552                         string bvp, vpabsolute = vp.Absolute;
553                         StringBuilder sb;
554                         string newline;
555                         int failedCount = 0;
556                         
557                         if (debug) {
558                                 newline = Environment.NewLine;
559                                 sb = new StringBuilder ("Code generation for certain virtual paths in a batch failed. Those files have been removed from the batch." + newline);
560                                 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
561                         } else {
562                                 newline = null;
563                                 sb = null;
564                         }
565                         
566                         List <BuildProvider> failedBuildProviders = null;
567                         foreach (BuildProvider bp in group) {
568                                 bvp = bp.VirtualPath;
569                                 if (HasCachedItemNoLock (bvp))
570                                         continue;
571                                 
572                                 try {
573                                         bp.GenerateCode (abuilder);
574                                 } catch (Exception ex) {
575                                         if (String.Compare (bvp, vpabsolute, stringComparer) == 0) {
576                                                 if (ex is CompilationException || ex is ParseException)
577                                                         throw;
578                                                 
579                                                 throw new HttpException ("Code generation failed.", ex);
580                                         }
581                                         
582                                         if (failedBuildProviders == null)
583                                                 failedBuildProviders = new List <BuildProvider> ();
584                                         failedBuildProviders.Add (bp);
585                                         failedCount++;
586                                         if (sb != null) {
587                                                 if (failedCount > 1)
588                                                         sb.Append (newline);
589                                                 
590                                                 sb.AppendFormat ("Failed file virtual path: {0}; Exception: {1}{2}{1}", bp.VirtualPath, newline, ex);
591                                         }
592                                         continue;
593                                 }
594                                 
595                                 deps = bp.ExtractDependencies ();
596                                 if (deps != null) {
597                                         foreach (var dep in deps) {
598                                                 bmci = GetCachedItemNoLock (dep.Key);
599                                                 if (bmci == null || bmci.BuiltAssembly == null)
600                                                         continue;
601                                                 abuilder.AddAssemblyReference (bmci.BuiltAssembly);
602                                         }
603                                 }
604                         }
605
606                         if (sb != null && failedCount > 0)
607                                 ShowDebugModeMessage (sb.ToString ());
608                         
609                         if (failedBuildProviders != null) {
610                                 foreach (BuildProvider bp in failedBuildProviders)
611                                         group.Remove (bp);
612                         }
613                         
614                         foreach (Assembly asm in referencedAssemblies) {
615                                 if (asm == null)
616                                         continue;
617                                 
618                                 abuilder.AddAssemblyReference (asm);
619                         }
620                         
621                         CompilerResults results  = abuilder.BuildAssembly (vp);
622                         
623                         // No results is not an error - it is possible that the assembly builder contained only .asmx and
624                         // .ashx files which had no body, just the directive. In such case, no code unit or code file is added
625                         // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
626                         // to be added to the cache.
627                         Assembly compiledAssembly = results != null ? results.CompiledAssembly : null;
628                         bool locked = false;
629                         try {
630 #if SYSTEMCORE_DEP
631                                 buildCacheLock.EnterWriteLock ();
632 #else
633                                 buildCacheLock.AcquireWriterLock (-1);
634 #endif
635                                 locked = true;
636                                 if (compiledAssembly != null)
637                                         referencedAssemblies.Add (compiledAssembly);
638                                 
639                                 foreach (BuildProvider bp in group) {
640                                         if (HasCachedItemNoLock (bp.VirtualPath))
641                                                 continue;
642                                         
643                                         StoreInCache (bp, compiledAssembly, results);
644                                 }
645                         } finally {
646                                 if (locked) {
647 #if SYSTEMCORE_DEP
648                                         buildCacheLock.ExitWriteLock ();
649 #else
650                                         buildCacheLock.ReleaseWriterLock ();
651 #endif
652                                 }
653                         }
654                 }
655                 
656                 static VirtualPath GetAbsoluteVirtualPath (string virtualPath)
657                 {
658                         string vp;
659
660                         if (!VirtualPathUtility.IsRooted (virtualPath)) {
661                                 HttpContext ctx = HttpContext.Current;
662                                 HttpRequest req = ctx != null ? ctx.Request : null;
663                                 
664                                 if (req != null) {
665                                         string fileDir = req.FilePath;
666                                         if (!String.IsNullOrEmpty (fileDir) && String.Compare (fileDir, "/", StringComparison.Ordinal) != 0)
667                                                 fileDir = VirtualPathUtility.GetDirectory (fileDir);
668                                         else
669                                                 fileDir = "/";
670
671                                         vp = VirtualPathUtility.Combine (fileDir, virtualPath);
672                                 } else
673                                         throw new HttpException ("No context, cannot map paths.");
674                         } else
675                                 vp = virtualPath;
676
677                         return new VirtualPath (vp);
678                 }
679
680                 [MonoTODO ("Not implemented, always returns null")]
681                 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath)
682                 {
683                         return null; // null is ok here until we store the dependency set in the Cache.
684                 }
685                 
686                 static BuildManagerCacheItem GetCachedItem (string vp)
687                 {
688                         bool locked = false;
689                         
690                         try {
691 #if SYSTEMCORE_DEP
692                                 buildCacheLock.EnterReadLock ();
693 #else
694                                 buildCacheLock.AcquireReaderLock (-1);
695 #endif
696                                 locked = true;
697                                 return GetCachedItemNoLock (vp);
698                         } finally {
699                                 if (locked) {
700 #if SYSTEMCORE_DEP
701                                         buildCacheLock.ExitReadLock ();
702 #else
703                                         buildCacheLock.ReleaseReaderLock ();
704 #endif
705                                 }
706                         }
707                 }
708
709                 static BuildManagerCacheItem GetCachedItemNoLock (string vp)
710                 {
711                         BuildManagerCacheItem ret;
712                         if (buildCache.TryGetValue (vp, out ret))
713                                 return ret;
714                         
715                         return null;
716                 }
717                 
718                 internal static Type GetCodeDomProviderType (BuildProvider provider)
719                 {
720                         CompilerType codeCompilerType;
721                         Type codeDomProviderType = null;
722
723                         codeCompilerType = provider.CodeCompilerType;
724                         if (codeCompilerType != null)
725                                 codeDomProviderType = codeCompilerType.CodeDomProviderType;
726                                 
727                         if (codeDomProviderType == null)
728                                 throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type."));
729
730                         return codeDomProviderType;
731                 }
732
733                 static Type GetPrecompiledType (string virtualPath)
734                 {
735                         PreCompilationData pc_data;
736                         if (precompiled != null && precompiled.TryGetValue (virtualPath, out pc_data)) {
737                                 if (pc_data.Type == null) {
738                                         pc_data.Type = Type.GetType (pc_data.TypeName + ", " + pc_data.AssemblyFileName, true);
739                                 }
740                                 return pc_data.Type;
741                         }
742                         return null;
743                 }
744
745                 internal static Type GetPrecompiledApplicationType ()
746                 {
747                         if (!is_precompiled)
748                                 return null;
749
750                         Type apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath, "Global.asax"));
751                         if (apptype == null)
752                                 apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath , "global.asax"));
753                         return apptype;
754                 }
755
756                 public static Assembly GetCompiledAssembly (string virtualPath)
757                 {
758                         return GetCompiledAssembly (GetAbsoluteVirtualPath (virtualPath));
759                 }
760
761                 internal static Assembly GetCompiledAssembly (VirtualPath virtualPath)
762                 {
763                         string vpabsolute = virtualPath.Absolute;
764                         if (is_precompiled) {
765                                 Type type = GetPrecompiledType (vpabsolute);
766                                 if (type != null)
767                                         return type.Assembly;
768                         }
769                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
770                         if (bmci != null)
771                                 return bmci.BuiltAssembly;
772
773                         Build (virtualPath);
774                         bmci = GetCachedItem (vpabsolute);
775                         if (bmci != null)
776                                 return bmci.BuiltAssembly;
777                         
778                         return null;
779                 }
780                 
781                 public static Type GetCompiledType (string virtualPath)
782                 {
783                         return GetCompiledType (GetAbsoluteVirtualPath (virtualPath));
784                 }
785
786                 internal static Type GetCompiledType (VirtualPath virtualPath)
787                 {
788                         string vpabsolute = virtualPath.Absolute;
789                         if (is_precompiled) {
790                                 Type type = GetPrecompiledType (vpabsolute);
791                                 if (type != null)
792                                         return type;
793                         }
794                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
795                         if (bmci != null) {
796                                 ReferenceAssemblyInCompilation (bmci);
797                                 return bmci.Type;
798                         }
799
800                         Build (virtualPath);
801                         bmci = GetCachedItem (vpabsolute);
802                         if (bmci != null) {
803                                 ReferenceAssemblyInCompilation (bmci);
804                                 return bmci.Type;
805                         }
806
807                         return null;
808                 }
809
810                 public static string GetCompiledCustomString (string virtualPath)
811                 {
812                         return GetCompiledCustomString (GetAbsoluteVirtualPath (virtualPath));
813                 }
814         
815                 internal static string GetCompiledCustomString (VirtualPath virtualPath) 
816                 {
817                         string vpabsolute = virtualPath.Absolute;
818                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
819                         if (bmci != null)
820                                 return bmci.CompiledCustomString;
821                         
822                         Build (virtualPath);
823                         bmci = GetCachedItem (vpabsolute);
824                         if (bmci != null)
825                                 return bmci.CompiledCustomString;
826                         
827                         return null;
828                 }
829
830                 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection)
831                 {
832                         return GetDefaultCompilerTypeForLanguage (language, configSection, true);
833                 }
834                 
835                 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection, bool throwOnMissing)
836                 {
837                         // MS throws when accesing a Hashtable, we do here.
838                         if (language == null || language.Length == 0)
839                                 throw new ArgumentNullException ("language");
840                                 
841                         CompilationSection config;
842                         if (configSection == null)
843                                 config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
844                         else
845                                 config = configSection;
846                         
847                         Compiler compiler = config.Compilers.Get (language);
848                         CompilerParameters p;
849                         Type type;
850                         
851                         if (compiler != null) {
852                                 type = HttpApplication.LoadType (compiler.Type, true);
853                                 p = new CompilerParameters ();
854                                 p.CompilerOptions = compiler.CompilerOptions;
855                                 p.WarningLevel = compiler.WarningLevel;
856                                 SetCommonParameters (config, p, type, language);
857                                 return new CompilerType (type, p);
858                         }
859
860                         if (CodeDomProvider.IsDefinedLanguage (language)) {
861                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (language);
862                                 p = info.CreateDefaultCompilerParameters ();
863                                 type = info.CodeDomProviderType;
864                                 SetCommonParameters (config, p, type, language);
865                                 return new CompilerType (type, p);
866                         }
867
868                         if (throwOnMissing)
869                                 throw new HttpException (String.Concat ("No compiler for language '", language, "'."));
870
871                         return null;
872                 }
873
874                 public static ICollection GetReferencedAssemblies ()
875                 {
876                         List <Assembly> al = new List <Assembly> ();
877                         
878                         CompilationSection compConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
879                         if (compConfig == null)
880                                 return al;
881                         
882                         bool addAssembliesInBin = false;
883                         foreach (AssemblyInfo info in compConfig.Assemblies) {
884                                 if (info.Assembly == "*")
885                                         addAssembliesInBin = is_precompiled ? false : true;
886                                 else
887                                         LoadAssembly (info, al);
888                         }
889
890                         foreach (Assembly topLevelAssembly in TopLevelAssemblies)
891                                 al.Add (topLevelAssembly);
892
893                         foreach (string assLocation in WebConfigurationManager.ExtraAssemblies)
894                                 LoadAssembly (assLocation, al);
895
896
897                         // Precompiled sites unconditionally load all assemblies from bin/ (fix for
898                         // bug #502016)
899                         if (is_precompiled || addAssembliesInBin) {
900                                 foreach (string s in HttpApplication.BinDirectoryAssemblies) {
901                                         try {
902                                                 LoadAssembly (s, al);
903                                         } catch (BadImageFormatException) {
904                                                 // ignore silently
905                                         }
906                                 }
907                         }
908                                 
909                         return al;
910                 }
911                 
912                 // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers
913                 public static Type GetType (string typeName, bool throwOnError)
914                 {
915                         return GetType (typeName, throwOnError, false);
916                 }
917
918                 public static Type GetType (string typeName, bool throwOnError, bool ignoreCase)
919                 {
920                         Type ret = null;
921                         try {
922                                 foreach (Assembly asm in TopLevel_Assemblies) {
923                                         ret = asm.GetType (typeName, throwOnError, ignoreCase);
924                                         if (ret != null)
925                                                 break;
926                                 }
927                         } catch (Exception ex) {
928                                 throw new HttpException ("Failed to find the specified type.", ex);
929                         }
930                         return ret;
931                 }
932
933                 public static IDictionary <string, bool> GetVirtualPathDependencies (string virtualPath)
934                 {
935                         return GetVirtualPathDependencies (virtualPath, null);
936                 }
937
938                 internal static IDictionary <string, bool> GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider)
939                 {
940                         BuildProvider provider = bprovider;
941                         if (provider == null) {
942                                 CompilationSection cs = CompilationConfig;
943                                 if (cs == null)
944                                         return null;
945                                 provider = BuildManagerDirectoryBuilder.GetBuildProvider (virtualPath, cs.BuildProviders);
946                         }
947                         
948                         if (provider == null)
949                                 return null;
950                         
951                         return provider.ExtractDependencies ();
952                 }
953
954                 internal static bool HasCachedItemNoLock (string vp)
955                 {
956                         return buildCache.ContainsKey (vp);
957                 }
958                 
959                 internal static bool IgnoreVirtualPath (string virtualPath)
960                 {
961                         if (!haveVirtualPathsToIgnore)
962                                 return false;
963                         
964                         if (virtualPathsToIgnore.ContainsKey (virtualPath))
965                                 return true;
966                         
967                         return false;
968                 }
969
970                 static bool IsSingleBuild (VirtualPath vp, bool recursive)
971                 {
972                         if (String.Compare (vp.AppRelative, "~/global.asax", StringComparison.OrdinalIgnoreCase) == 0)
973                                 return true;
974
975                         if (!BatchMode)
976                                 return true;
977                         
978                         return recursive;
979                 }
980                 
981                 static void LoadAssembly (string path, List <Assembly> al)
982                 {
983                         AddAssembly (Assembly.LoadFrom (path), al);
984                 }
985
986                 static void LoadAssembly (AssemblyInfo info, List <Assembly> al)
987                 {
988                         AddAssembly (Assembly.Load (info.Assembly), al);
989                 }
990                 
991                 static void LoadVirtualPathsToIgnore ()
992                 {
993                         NameValueCollection appSettings = WebConfigurationManager.AppSettings;
994                         if (appSettings == null)
995                                 return;
996
997                         string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"];
998                         string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"];
999
1000                         if (!String.IsNullOrEmpty (pathsFromConfig)) {
1001                                 string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars);
1002                                 string path;
1003                                 
1004                                 foreach (string p in paths) {
1005                                         path = p.Trim ();
1006                                         if (path.Length == 0)
1007                                                 continue;
1008
1009                                         AddPathToIgnore (path);
1010                                 }
1011                         }
1012
1013                         if (!String.IsNullOrEmpty (pathsFromFile)) {
1014                                 string realpath;
1015                                 HttpContext ctx = HttpContext.Current;
1016                                 HttpRequest req = ctx != null ? ctx.Request : null;
1017
1018                                 if (req == null)
1019                                         throw new HttpException ("Missing context, cannot continue.");
1020
1021                                 realpath = req.MapPath (pathsFromFile);
1022                                 if (!File.Exists (realpath))
1023                                         return;
1024
1025                                 string[] paths = File.ReadAllLines (realpath);
1026                                 if (paths == null || paths.Length == 0)
1027                                         return;
1028
1029                                 string path;
1030                                 foreach (string p in paths) {
1031                                         path = p.Trim ();
1032                                         if (path.Length == 0)
1033                                                 continue;
1034
1035                                         AddPathToIgnore (path);
1036                                 }
1037                         }
1038                 }
1039
1040                 static void OnEntryRemoved (string vp)
1041                 {
1042                         BuildManagerRemoveEntryEventHandler eh = events [buildManagerRemoveEntryEvent] as BuildManagerRemoveEntryEventHandler;
1043
1044                         if (eh != null)
1045                                 eh (new BuildManagerRemoveEntryEventArgs (vp, HttpContext.Current));
1046                 }
1047                 
1048                 static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason)
1049                 {
1050                         string virtualPath;
1051
1052                         if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX))
1053                                 virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH);
1054                         else
1055                                 return;
1056                         
1057                         bool locked = false;
1058                         try {
1059 #if SYSTEMCORE_DEP
1060                                 buildCacheLock.EnterWriteLock ();
1061 #else
1062                                 buildCacheLock.AcquireWriterLock (-1);
1063 #endif
1064                                 locked = true;
1065
1066                                 if (HasCachedItemNoLock (virtualPath)) {
1067                                         buildCache.Remove (virtualPath);
1068                                         OnEntryRemoved (virtualPath);
1069                                 }
1070                         } finally {
1071                                 if (locked) {
1072 #if SYSTEMCORE_DEP
1073                                         buildCacheLock.ExitWriteLock ();
1074 #else
1075                                         buildCacheLock.ReleaseWriterLock ();
1076 #endif
1077                                 }
1078                         }
1079                 }
1080                 
1081                 static void ReferenceAssemblyInCompilation (BuildManagerCacheItem bmci)
1082                 {
1083                         if (recursionDepth == 0 || referencedAssemblies.Contains (bmci.BuiltAssembly))
1084                                 return;
1085
1086                         referencedAssemblies.Add (bmci.BuiltAssembly);
1087                 }
1088                 
1089                 static void RemoveFailedAssemblies (string requestedVirtualPath, CompilationException ex, AssemblyBuilder abuilder,
1090                                                     BuildProviderGroup group, CompilerResults results, bool debug)
1091                 {
1092                         StringBuilder sb;
1093                         string newline;
1094                         
1095                         if (debug) {
1096                                 newline = Environment.NewLine;
1097                                 sb = new StringBuilder ("Compilation of certain files in a batch failed. Another attempt to compile the batch will be made." + newline);
1098                                 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
1099                         } else {
1100                                 newline = null;
1101                                 sb = null;
1102                         }
1103                         
1104                         var failedBuildProviders = new List <BuildProvider> ();
1105                         BuildProvider bp;
1106                         HttpContext ctx = HttpContext.Current;
1107                         HttpRequest req = ctx != null ? ctx.Request : null;
1108                         bool rethrow = false;
1109                         
1110                         foreach (CompilerError error in results.Errors) {
1111                                 if (error.IsWarning)
1112                                         continue;
1113                                 
1114                                 bp = abuilder.GetBuildProviderForPhysicalFilePath (error.FileName);
1115                                 if (bp == null) {
1116                                         bp = FindBuildProviderForPhysicalPath (error.FileName, group, req);
1117                                         if (bp == null)
1118                                                 continue;
1119                                 }
1120
1121                                 if (String.Compare (bp.VirtualPath, requestedVirtualPath, StringComparison.Ordinal) == 0)
1122                                         rethrow = true;
1123
1124                                 if (!failedBuildProviders.Contains (bp)) {
1125                                         failedBuildProviders.Add (bp);
1126                                         if (sb != null)
1127                                                 sb.AppendFormat ("\t{0}{1}", bp.VirtualPath, newline);
1128                                 }
1129
1130                                 if (sb != null)
1131                                         sb.AppendFormat ("\t\t{0}{1}", error, newline);
1132                         }
1133
1134                         foreach (BuildProvider fbp in failedBuildProviders)
1135                                 group.Remove (fbp);
1136                         
1137                         if (sb != null) {
1138                                 sb.AppendFormat ("{0}The following exception has been thrown for the file(s) listed above:{0}{1}",
1139                                                  newline, ex.ToString ());
1140                                 ShowDebugModeMessage (sb.ToString ());
1141                                 sb = null;
1142                         }
1143
1144                         if (rethrow)
1145                                 throw new HttpException ("Compilation failed.", ex);
1146                 }
1147                 
1148                 static void SetCommonParameters (CompilationSection config, CompilerParameters p, Type compilerType, string language)
1149                 {
1150                         p.IncludeDebugInformation = config.Debug;
1151                         MonoSettingsSection mss = WebConfigurationManager.GetSection ("system.web/monoSettings") as MonoSettingsSection;
1152                         if (mss == null || !mss.UseCompilersCompatibility)
1153                                 return;
1154
1155                         Compiler compiler = mss.CompilersCompatibility.Get (language);
1156                         if (compiler == null)
1157                                 return;
1158
1159                         Type type = HttpApplication.LoadType (compiler.Type, false);
1160                         if (type != compilerType)
1161                                 return;
1162
1163                         p.CompilerOptions = String.Concat (p.CompilerOptions, " ", compiler.CompilerOptions);
1164                 }
1165
1166                 static void ShowDebugModeMessage (string msg)
1167                 {
1168                         if (suppressDebugModeMessages)
1169                                 return;
1170                         
1171                         Console.WriteLine ();
1172                         Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1173                         Console.WriteLine (msg);
1174                         Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1175                         Console.WriteLine ();
1176                 }
1177
1178                 static void StoreInCache (BuildProvider bp, Assembly compiledAssembly, CompilerResults results)
1179                 {
1180                         buildCache.Add (bp.VirtualPath, new BuildManagerCacheItem (compiledAssembly, bp, results));
1181                         
1182                         HttpContext ctx = HttpContext.Current;
1183                         HttpRequest req = ctx != null ? ctx.Request : null;
1184                         CacheDependency dep;
1185                         string virtualPath = bp.VirtualPath;
1186                         
1187                         if (req != null) {
1188                                 IDictionary <string, bool> deps = bp.ExtractDependencies ();
1189                                 var files = new List <string> ();
1190                                 string physicalPath;
1191
1192                                 physicalPath = req.MapPath (virtualPath);
1193                                 if (File.Exists (physicalPath))
1194                                         files.Add (physicalPath);
1195                                 
1196                                 if (deps != null && deps.Count > 0) {
1197                                         foreach (var d in deps) {
1198                                                 physicalPath = req.MapPath (d.Key);
1199                                                 if (!File.Exists (physicalPath))
1200                                                         continue;
1201                                                 if (!files.Contains (physicalPath))
1202                                                         files.Add (physicalPath);
1203                                         }
1204                                 }
1205
1206                                 dep = new CacheDependency (files.ToArray ());
1207                         } else
1208                                 dep = null;
1209
1210                         HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath,
1211                                                        true,
1212                                                        dep,
1213                                                        Cache.NoAbsoluteExpiration,
1214                                                        Cache.NoSlidingExpiration,
1215                                                        CacheItemPriority.High,
1216                                                        new CacheItemRemovedCallback (OnVirtualPathChanged));
1217                 }
1218         }
1219 }
1220