2010-01-20 Zoltan Varga <vargaz@gmail.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                                         // 2 -> ashx
236                                         // 3 -> ascx, aspx
237                                         // 6 -> app_code - nothing to do here
238                                         // 8 -> global.asax
239                                         // 9 -> App_GlobalResources - set the assembly for HttpContext
240                                         if (reader.Name == "resultType" && (val == "2" || val == "3" || val == "8"))
241                                                 LoadPageData (reader, true);
242                                         else if (val == "9") {
243                                                 PreCompilationData pd = LoadPageData (reader, false);
244                                                 HttpContext.AppGlobalResourcesAssembly = Assembly.Load (pd.AssemblyFileName);
245                                         }
246                                 }
247                         }
248                 }
249
250                 class PreCompilationData {
251                         public string VirtualPath;
252                         public string AssemblyFileName;
253                         public string TypeName;
254                         public Type Type;
255                 }
256
257                 static PreCompilationData LoadPageData (XmlTextReader reader, bool store)
258                 {
259                         PreCompilationData pc_data = new PreCompilationData ();
260
261                         while (reader.MoveToNextAttribute ()) {
262                                 string name = reader.Name;
263                                 if (name == "virtualPath")
264                                         pc_data.VirtualPath = VirtualPathUtility.RemoveTrailingSlash (reader.Value);
265                                 else if (name == "assembly")
266                                         pc_data.AssemblyFileName = reader.Value;
267                                 else if (name == "type")
268                                         pc_data.TypeName = reader.Value;
269                         }
270                         if (store) {
271                                 if (precompiled == null)
272                                         precompiled = new Dictionary<string, PreCompilationData> (comparer);
273                                 precompiled.Add (pc_data.VirtualPath, pc_data);
274                         }
275                         return pc_data;
276                 }
277
278                 static void AddAssembly (Assembly asm, List <Assembly> al)
279                 {
280                         if (al.Contains (asm))
281                                 return;
282
283                         al.Add (asm);
284                 }
285                 
286                 static void AddPathToIgnore (string vp)
287                 {
288                         if (virtualPathsToIgnore == null)
289                                 virtualPathsToIgnore = new Dictionary <string, bool> (comparer);
290                         
291                         VirtualPath path = GetAbsoluteVirtualPath (vp);
292                         string vpAbsolute = path.Absolute;
293                         if (!virtualPathsToIgnore.ContainsKey (vpAbsolute)) {
294                                 virtualPathsToIgnore.Add (vpAbsolute, true);
295                                 haveVirtualPathsToIgnore = true;
296                         }
297                         
298                         string vpRelative = path.AppRelative;
299                         if (!virtualPathsToIgnore.ContainsKey (vpRelative)) {
300                                 virtualPathsToIgnore.Add (vpRelative, true);
301                                 haveVirtualPathsToIgnore = true;
302                         }
303
304                         if (!virtualPathsToIgnore.ContainsKey (vp)) {
305                                 virtualPathsToIgnore.Add (vp, true);
306                                 haveVirtualPathsToIgnore = true;
307                         }
308                 }
309
310                 internal static void AddToReferencedAssemblies (Assembly asm)
311                 {
312                         // should not be used
313                 }
314                 
315                 static void AssertVirtualPathExists (VirtualPath virtualPath)
316                 {
317                         string realpath;
318                         bool dothrow = false;
319                         
320                         if (virtualPath.IsFake) {
321                                 realpath = virtualPath.PhysicalPath;
322                                 if (!File.Exists (realpath) && !Directory.Exists (realpath))
323                                         dothrow = true;
324                         } else {
325                                 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
326                                 string vpAbsolute = virtualPath.Absolute;
327                                 
328                                 if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute))
329                                         dothrow = true;
330                         }
331
332                         if (dothrow)
333                                 throw new HttpException (404, "The file '" + virtualPath + "' does not exist.", virtualPath.Absolute);
334                 }
335
336                 static void Build (VirtualPath vp)
337                 {
338                         AssertVirtualPathExists (vp);
339
340                         CompilationSection cs = CompilationConfig;
341                         lock (bigCompilationLock) {
342                                 if (HasCachedItemNoLock (vp.Absolute))
343                                         return;
344
345                                 if (recursionDepth == 0)
346                                         referencedAssemblies.Clear ();
347
348                                 recursionDepth++;
349                                 try {
350                                         BuildInner (vp, cs != null ? cs.Debug : false);
351                                         if (recursionDepth <= 1)
352                                                 buildCount++;
353
354                                         // See http://support.microsoft.com/kb/319947
355                                         if (buildCount > cs.NumRecompilesBeforeAppRestart)
356                                                 HttpRuntime.UnloadAppDomain ();
357                                 } finally {
358                                         recursionDepth--;
359                                 }
360                         }
361                 }
362
363                 // This method assumes it is being called with the big compilation lock held
364                 static void BuildInner (VirtualPath vp, bool debug)
365                 {
366                         var builder = new BuildManagerDirectoryBuilder (vp);
367                         bool recursive = recursionDepth > 1;
368                         List <BuildProviderGroup> builderGroups = builder.Build (IsSingleBuild (vp, recursive));
369                         if (builderGroups == null)
370                                 return;
371
372                         string vpabsolute = vp.Absolute;
373                         int buildHash = (vpabsolute.GetHashCode () | (int)DateTime.Now.Ticks) + (int)recursionDepth;
374                         string assemblyBaseName;
375                         AssemblyBuilder abuilder;
376                         CompilerType ct;
377                         int attempts;
378                         bool singleBuild, needMainVpBuild;
379                         CompilationException compilationError;
380                         
381                         // Each group becomes a separate assembly.
382                         foreach (BuildProviderGroup group in builderGroups) {
383                                 needMainVpBuild = false;
384                                 compilationError = null;
385                                 assemblyBaseName = null;
386                                 
387                                 if (group.Count == 1) {
388                                         if (recursive || !group.Master)
389                                                 assemblyBaseName = String.Format ("{0}_{1}.{2:x}.", group.NamePrefix, VirtualPathUtility.GetFileName (group [0].VirtualPath), buildHash);
390                                         singleBuild = true;
391                                 } else
392                                         singleBuild = false;
393                                 
394                                 if (assemblyBaseName == null)
395                                         assemblyBaseName = group.NamePrefix + "_";
396                                 
397                                 ct = group.CompilerType;
398                                 attempts = 3;
399                                 while (attempts > 0) {
400                                         abuilder = new AssemblyBuilder (vp, CreateDomProvider (ct), assemblyBaseName);
401                                         abuilder.CompilerOptions = ct.CompilerParameters;
402                                         abuilder.AddAssemblyReference (GetReferencedAssemblies () as List <Assembly>);
403                                         try {
404                                                 GenerateAssembly (abuilder, group, vp, debug);
405                                                 attempts = 0;
406                                         } catch (CompilationException ex) {
407                                                 attempts--;
408                                                 if (singleBuild)
409                                                         throw new HttpException ("Single file build failed.", ex);
410                                                 
411                                                 if (attempts == 0) {
412                                                         needMainVpBuild = true;
413                                                         compilationError = ex;
414                                                         break;
415                                                 }
416                                                 
417                                                 CompilerResults results = ex.Results;
418                                                 if (results == null)
419                                                         throw new HttpException ("No results returned from failed compilation.", ex);
420                                                 else
421                                                         RemoveFailedAssemblies (vpabsolute, ex, abuilder, group, results, debug);
422                                         }
423                                 }
424
425                                 if (needMainVpBuild) {
426                                         // One last attempt - try to build just the requested path
427                                         // if it's not built yet or just return without throwing the
428                                         // exception if it has already been built. 
429                                         if (HasCachedItemNoLock (vpabsolute)) {
430                                                 if (debug)
431                                                         DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
432                                                                                   compilationError, vpabsolute);
433                                                 return;
434                                         };
435
436                                         // This will trigger a recursive build of the requested vp,
437                                         // which means only the vp alone will be built (or not); 
438                                         Build (vp);
439                                         if (HasCachedItemNoLock (vpabsolute)) {
440                                                 if (debug)
441                                                         DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
442                                                                                   compilationError, vpabsolute);
443                                                 return;
444                                         }
445
446                                         // In theory this code is unreachable. If the recursive
447                                         // build of the main vp failed, then it should have thrown
448                                         // the build exception.
449                                         throw new HttpException ("Requested virtual path build failed.", compilationError);
450                                 }
451                         }
452                 }
453                 
454                 static CodeDomProvider CreateDomProvider (CompilerType ct)
455                 {
456                         if (codeDomProviders == null)
457                                 codeDomProviders = new Dictionary <Type, CodeDomProvider> ();
458
459                         Type type = ct.CodeDomProviderType;
460                         if (type == null) {
461                                 CompilationSection cs = CompilationConfig;
462                                 CompilerType tmp = GetDefaultCompilerTypeForLanguage (cs.DefaultLanguage, cs);
463                                 if (tmp != null)
464                                         type = tmp.CodeDomProviderType;
465                         }
466
467                         if (type == null)
468                                 return null;
469                         
470                         CodeDomProvider ret;
471                         if (codeDomProviders.TryGetValue (type, out ret))
472                                 return ret;
473
474                         ret = Activator.CreateInstance (type) as CodeDomProvider;
475                         if (ret == null)
476                                 return null;
477
478                         codeDomProviders.Add (type, ret);
479                         return ret;
480                 }
481                 
482                 public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType)
483                 {
484                         return CreateInstanceFromVirtualPath (GetAbsoluteVirtualPath (virtualPath), requiredBaseType);
485                 }
486
487                 internal static object CreateInstanceFromVirtualPath (VirtualPath virtualPath, Type requiredBaseType)
488                 {
489                         if (requiredBaseType == null)
490                                 throw new NullReferenceException (); // This is what MS does, but
491                                                                      // from somewhere else.
492                         
493                         Type type = GetCompiledType (virtualPath);
494                         if (type == null)
495                                 return null;
496
497                         if (!requiredBaseType.IsAssignableFrom (type))
498                                 throw new HttpException (500,
499                                                          String.Format ("Type '{0}' does not inherit from '{1}'.", type.FullName, requiredBaseType.FullName));
500
501                         return Activator.CreateInstance (type, null);
502                 }
503                 
504                 static void DescribeCompilationError (string format, CompilationException ex, params object[] parms)
505                 {
506                         StringBuilder sb = new StringBuilder ();
507                         string newline = Environment.NewLine;
508                         
509                         if (parms != null)
510                                 sb.AppendFormat (format + newline, parms);
511                         else
512                                 sb.Append (format + newline);
513
514                         CompilerResults results = ex != null ? ex.Results : null;
515                         if (results == null)
516                                 sb.Append ("No compiler error information present." + newline);
517                         else {
518                                 sb.Append ("Compiler errors:" + newline);
519                                 foreach (CompilerError error in results.Errors)
520                                         sb.Append ("  " + error.ToString () + newline);
521                         }
522
523                         if (ex != null) {
524                                 sb.Append (newline + "Exception thrown:" + newline);
525                                 sb.Append (ex.ToString ());
526                         }
527
528                         ShowDebugModeMessage (sb.ToString ());
529                 }
530                 
531                 static BuildProvider FindBuildProviderForPhysicalPath (string path, BuildProviderGroup group, HttpRequest req)
532                 {
533                         if (req == null || String.IsNullOrEmpty (path))
534                                 return null;
535
536                         foreach (BuildProvider bp in group) {
537                                 if (String.Compare (path, req.MapPath (bp.VirtualPath), stringComparer) == 0)
538                                         return bp;
539                         }
540                         
541                         return null;
542                 }
543                 
544                 static void GenerateAssembly (AssemblyBuilder abuilder, BuildProviderGroup group, VirtualPath vp, bool debug)
545                 {
546                         IDictionary <string, bool> deps;
547                         BuildManagerCacheItem bmci;
548                         string bvp, vpabsolute = vp.Absolute;
549                         StringBuilder sb;
550                         string newline;
551                         int failedCount = 0;
552                         
553                         if (debug) {
554                                 newline = Environment.NewLine;
555                                 sb = new StringBuilder ("Code generation for certain virtual paths in a batch failed. Those files have been removed from the batch." + newline);
556                                 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
557                         } else {
558                                 newline = null;
559                                 sb = null;
560                         }
561                         
562                         List <BuildProvider> failedBuildProviders = null;
563                         foreach (BuildProvider bp in group) {
564                                 bvp = bp.VirtualPath;
565                                 if (HasCachedItemNoLock (bvp))
566                                         continue;
567                                 
568                                 try {
569                                         bp.GenerateCode (abuilder);
570                                 } catch (Exception ex) {
571                                         if (String.Compare (bvp, vpabsolute, stringComparer) == 0) {
572                                                 if (ex is CompilationException || ex is ParseException)
573                                                         throw;
574                                                 
575                                                 throw new HttpException ("Code generation failed.", ex);
576                                         }
577                                         
578                                         if (failedBuildProviders == null)
579                                                 failedBuildProviders = new List <BuildProvider> ();
580                                         failedBuildProviders.Add (bp);
581                                         failedCount++;
582                                         if (sb != null) {
583                                                 if (failedCount > 1)
584                                                         sb.Append (newline);
585                                                 
586                                                 sb.AppendFormat ("Failed file virtual path: {0}; Exception: {1}{2}{1}", bp.VirtualPath, newline, ex);
587                                         }
588                                         continue;
589                                 }
590                                 
591                                 deps = bp.ExtractDependencies ();
592                                 if (deps != null) {
593                                         foreach (var dep in deps) {
594                                                 bmci = GetCachedItemNoLock (dep.Key);
595                                                 if (bmci == null || bmci.BuiltAssembly == null)
596                                                         continue;
597                                                 abuilder.AddAssemblyReference (bmci.BuiltAssembly);
598                                         }
599                                 }
600                         }
601
602                         if (sb != null && failedCount > 0)
603                                 ShowDebugModeMessage (sb.ToString ());
604                         
605                         if (failedBuildProviders != null) {
606                                 foreach (BuildProvider bp in failedBuildProviders)
607                                         group.Remove (bp);
608                         }
609                         
610                         foreach (Assembly asm in referencedAssemblies) {
611                                 if (asm == null)
612                                         continue;
613                                 
614                                 abuilder.AddAssemblyReference (asm);
615                         }
616                         
617                         CompilerResults results  = abuilder.BuildAssembly (vp);
618                         
619                         // No results is not an error - it is possible that the assembly builder contained only .asmx and
620                         // .ashx files which had no body, just the directive. In such case, no code unit or code file is added
621                         // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
622                         // to be added to the cache.
623                         Assembly compiledAssembly = results != null ? results.CompiledAssembly : null;
624                         bool locked = false;
625                         try {
626 #if SYSTEMCORE_DEP
627                                 buildCacheLock.EnterWriteLock ();
628 #else
629                                 buildCacheLock.AcquireWriterLock (-1);
630 #endif
631                                 locked = true;
632                                 if (compiledAssembly != null)
633                                         referencedAssemblies.Add (compiledAssembly);
634                                 
635                                 foreach (BuildProvider bp in group) {
636                                         if (HasCachedItemNoLock (bp.VirtualPath))
637                                                 continue;
638                                         
639                                         StoreInCache (bp, compiledAssembly, results);
640                                 }
641                         } finally {
642                                 if (locked) {
643 #if SYSTEMCORE_DEP
644                                         buildCacheLock.ExitWriteLock ();
645 #else
646                                         buildCacheLock.ReleaseWriterLock ();
647 #endif
648                                 }
649                         }
650                 }
651                 
652                 static VirtualPath GetAbsoluteVirtualPath (string virtualPath)
653                 {
654                         string vp;
655
656                         if (!VirtualPathUtility.IsRooted (virtualPath)) {
657                                 HttpContext ctx = HttpContext.Current;
658                                 HttpRequest req = ctx != null ? ctx.Request : null;
659                                 
660                                 if (req != null) {
661                                         string fileDir = req.FilePath;
662                                         if (!String.IsNullOrEmpty (fileDir) && String.Compare (fileDir, "/", StringComparison.Ordinal) != 0)
663                                                 fileDir = VirtualPathUtility.GetDirectory (fileDir);
664                                         else
665                                                 fileDir = "/";
666
667                                         vp = VirtualPathUtility.Combine (fileDir, virtualPath);
668                                 } else
669                                         throw new HttpException ("No context, cannot map paths.");
670                         } else
671                                 vp = virtualPath;
672
673                         return new VirtualPath (vp);
674                 }
675
676                 [MonoTODO ("Not implemented, always returns null")]
677                 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath)
678                 {
679                         return null; // null is ok here until we store the dependency set in the Cache.
680                 }
681                 
682                 static BuildManagerCacheItem GetCachedItem (string vp)
683                 {
684                         bool locked = false;
685                         
686                         try {
687 #if SYSTEMCORE_DEP
688                                 buildCacheLock.EnterReadLock ();
689 #else
690                                 buildCacheLock.AcquireReaderLock (-1);
691 #endif
692                                 locked = true;
693                                 return GetCachedItemNoLock (vp);
694                         } finally {
695                                 if (locked) {
696 #if SYSTEMCORE_DEP
697                                         buildCacheLock.ExitReadLock ();
698 #else
699                                         buildCacheLock.ReleaseReaderLock ();
700 #endif
701                                 }
702                         }
703                 }
704
705                 static BuildManagerCacheItem GetCachedItemNoLock (string vp)
706                 {
707                         BuildManagerCacheItem ret;
708                         if (buildCache.TryGetValue (vp, out ret))
709                                 return ret;
710                         
711                         return null;
712                 }
713                 
714                 internal static Type GetCodeDomProviderType (BuildProvider provider)
715                 {
716                         CompilerType codeCompilerType;
717                         Type codeDomProviderType = null;
718
719                         codeCompilerType = provider.CodeCompilerType;
720                         if (codeCompilerType != null)
721                                 codeDomProviderType = codeCompilerType.CodeDomProviderType;
722                                 
723                         if (codeDomProviderType == null)
724                                 throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type."));
725
726                         return codeDomProviderType;
727                 }
728
729                 static Type GetPrecompiledType (string virtualPath)
730                 {
731                         PreCompilationData pc_data;
732                         if (precompiled != null && precompiled.TryGetValue (virtualPath, out pc_data)) {
733                                 if (pc_data.Type == null) {
734                                         pc_data.Type = Type.GetType (pc_data.TypeName + ", " + pc_data.AssemblyFileName, true);
735                                 }
736                                 return pc_data.Type;
737                         }
738                         return null;
739                 }
740
741                 internal static Type GetPrecompiledApplicationType ()
742                 {
743                         if (!is_precompiled)
744                                 return null;
745
746                         Type apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath, "Global.asax"));
747                         if (apptype == null)
748                                 apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath , "global.asax"));
749                         return apptype;
750                 }
751
752                 public static Assembly GetCompiledAssembly (string virtualPath)
753                 {
754                         return GetCompiledAssembly (GetAbsoluteVirtualPath (virtualPath));
755                 }
756
757                 internal static Assembly GetCompiledAssembly (VirtualPath virtualPath)
758                 {
759                         string vpabsolute = virtualPath.Absolute;
760                         if (is_precompiled) {
761                                 Type type = GetPrecompiledType (vpabsolute);
762                                 if (type != null)
763                                         return type.Assembly;
764                         }
765                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
766                         if (bmci != null)
767                                 return bmci.BuiltAssembly;
768
769                         Build (virtualPath);
770                         bmci = GetCachedItem (vpabsolute);
771                         if (bmci != null)
772                                 return bmci.BuiltAssembly;
773                         
774                         return null;
775                 }
776                 
777                 public static Type GetCompiledType (string virtualPath)
778                 {
779                         return GetCompiledType (GetAbsoluteVirtualPath (virtualPath));
780                 }
781
782                 internal static Type GetCompiledType (VirtualPath virtualPath)
783                 {
784                         string vpabsolute = virtualPath.Absolute;
785                         if (is_precompiled) {
786                                 Type type = GetPrecompiledType (vpabsolute);
787                                 if (type != null)
788                                         return type;
789                         }
790                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
791                         if (bmci != null) {
792                                 ReferenceAssemblyInCompilation (bmci);
793                                 return bmci.Type;
794                         }
795
796                         Build (virtualPath);
797                         bmci = GetCachedItem (vpabsolute);
798                         if (bmci != null) {
799                                 ReferenceAssemblyInCompilation (bmci);
800                                 return bmci.Type;
801                         }
802
803                         return null;
804                 }
805
806                 public static string GetCompiledCustomString (string virtualPath)
807                 {
808                         return GetCompiledCustomString (GetAbsoluteVirtualPath (virtualPath));
809                 }
810         
811                 internal static string GetCompiledCustomString (VirtualPath virtualPath) 
812                 {
813                         string vpabsolute = virtualPath.Absolute;
814                         BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
815                         if (bmci != null)
816                                 return bmci.CompiledCustomString;
817                         
818                         Build (virtualPath);
819                         bmci = GetCachedItem (vpabsolute);
820                         if (bmci != null)
821                                 return bmci.CompiledCustomString;
822                         
823                         return null;
824                 }
825
826                 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection)
827                 {
828                         return GetDefaultCompilerTypeForLanguage (language, configSection, true);
829                 }
830                 
831                 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection, bool throwOnMissing)
832                 {
833                         // MS throws when accesing a Hashtable, we do here.
834                         if (language == null || language.Length == 0)
835                                 throw new ArgumentNullException ("language");
836                                 
837                         CompilationSection config;
838                         if (configSection == null)
839                                 config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
840                         else
841                                 config = configSection;
842                         
843                         Compiler compiler = config.Compilers.Get (language);
844                         CompilerParameters p;
845                         Type type;
846                         
847                         if (compiler != null) {
848                                 type = HttpApplication.LoadType (compiler.Type, true);
849                                 p = new CompilerParameters ();
850                                 p.CompilerOptions = compiler.CompilerOptions;
851                                 p.WarningLevel = compiler.WarningLevel;
852                                 SetCommonParameters (config, p, type, language);
853                                 return new CompilerType (type, p);
854                         }
855
856                         if (CodeDomProvider.IsDefinedLanguage (language)) {
857                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (language);
858                                 p = info.CreateDefaultCompilerParameters ();
859                                 type = info.CodeDomProviderType;
860                                 SetCommonParameters (config, p, type, language);
861                                 return new CompilerType (type, p);
862                         }
863
864                         if (throwOnMissing)
865                                 throw new HttpException (String.Concat ("No compiler for language '", language, "'."));
866
867                         return null;
868                 }
869
870                 public static ICollection GetReferencedAssemblies ()
871                 {
872                         List <Assembly> al = new List <Assembly> ();
873                         
874                         CompilationSection compConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
875                         if (compConfig == null)
876                                 return al;
877                         
878                         bool addAssembliesInBin = false;
879                         foreach (AssemblyInfo info in compConfig.Assemblies) {
880                                 if (info.Assembly == "*")
881                                         addAssembliesInBin = is_precompiled ? false : true;
882                                 else
883                                         LoadAssembly (info, al);
884                         }
885
886                         foreach (Assembly topLevelAssembly in TopLevelAssemblies)
887                                 al.Add (topLevelAssembly);
888
889                         foreach (string assLocation in WebConfigurationManager.ExtraAssemblies)
890                                 LoadAssembly (assLocation, al);
891
892
893                         // Precompiled sites unconditionally load all assemblies from bin/ (fix for
894                         // bug #502016)
895                         if (is_precompiled || addAssembliesInBin) {
896                                 foreach (string s in HttpApplication.BinDirectoryAssemblies) {
897                                         try {
898                                                 LoadAssembly (s, al);
899                                         } catch (BadImageFormatException) {
900                                                 // ignore silently
901                                         }
902                                 }
903                         }
904                                 
905                         return al;
906                 }
907                 
908                 // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers
909                 public static Type GetType (string typeName, bool throwOnError)
910                 {
911                         return GetType (typeName, throwOnError, false);
912                 }
913
914                 public static Type GetType (string typeName, bool throwOnError, bool ignoreCase)
915                 {
916                         Type ret = null;
917                         try {
918                                 foreach (Assembly asm in TopLevel_Assemblies) {
919                                         ret = asm.GetType (typeName, throwOnError, ignoreCase);
920                                         if (ret != null)
921                                                 break;
922                                 }
923                         } catch (Exception ex) {
924                                 throw new HttpException ("Failed to find the specified type.", ex);
925                         }
926                         return ret;
927                 }
928
929                 public static IDictionary <string, bool> GetVirtualPathDependencies (string virtualPath)
930                 {
931                         return GetVirtualPathDependencies (virtualPath, null);
932                 }
933
934                 internal static IDictionary <string, bool> GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider)
935                 {
936                         BuildProvider provider = bprovider;
937                         if (provider == null) {
938                                 CompilationSection cs = CompilationConfig;
939                                 if (cs == null)
940                                         return null;
941                                 provider = BuildManagerDirectoryBuilder.GetBuildProvider (virtualPath, cs.BuildProviders);
942                         }
943                         
944                         if (provider == null)
945                                 return null;
946                         
947                         return provider.ExtractDependencies ();
948                 }
949
950                 internal static bool HasCachedItemNoLock (string vp)
951                 {
952                         return buildCache.ContainsKey (vp);
953                 }
954                 
955                 internal static bool IgnoreVirtualPath (string virtualPath)
956                 {
957                         if (!haveVirtualPathsToIgnore)
958                                 return false;
959                         
960                         if (virtualPathsToIgnore.ContainsKey (virtualPath))
961                                 return true;
962                         
963                         return false;
964                 }
965
966                 static bool IsSingleBuild (VirtualPath vp, bool recursive)
967                 {
968                         if (String.Compare (vp.AppRelative, "~/global.asax", StringComparison.OrdinalIgnoreCase) == 0)
969                                 return true;
970
971                         if (!BatchMode)
972                                 return true;
973                         
974                         return recursive;
975                 }
976                 
977                 static void LoadAssembly (string path, List <Assembly> al)
978                 {
979                         AddAssembly (Assembly.LoadFrom (path), al);
980                 }
981
982                 static void LoadAssembly (AssemblyInfo info, List <Assembly> al)
983                 {
984                         AddAssembly (Assembly.Load (info.Assembly), al);
985                 }
986                 
987                 static void LoadVirtualPathsToIgnore ()
988                 {
989                         NameValueCollection appSettings = WebConfigurationManager.AppSettings;
990                         if (appSettings == null)
991                                 return;
992
993                         string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"];
994                         string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"];
995
996                         if (!String.IsNullOrEmpty (pathsFromConfig)) {
997                                 string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars);
998                                 string path;
999                                 
1000                                 foreach (string p in paths) {
1001                                         path = p.Trim ();
1002                                         if (path.Length == 0)
1003                                                 continue;
1004
1005                                         AddPathToIgnore (path);
1006                                 }
1007                         }
1008
1009                         if (!String.IsNullOrEmpty (pathsFromFile)) {
1010                                 string realpath;
1011                                 HttpContext ctx = HttpContext.Current;
1012                                 HttpRequest req = ctx != null ? ctx.Request : null;
1013
1014                                 if (req == null)
1015                                         throw new HttpException ("Missing context, cannot continue.");
1016
1017                                 realpath = req.MapPath (pathsFromFile);
1018                                 if (!File.Exists (realpath))
1019                                         return;
1020
1021                                 string[] paths = File.ReadAllLines (realpath);
1022                                 if (paths == null || paths.Length == 0)
1023                                         return;
1024
1025                                 string path;
1026                                 foreach (string p in paths) {
1027                                         path = p.Trim ();
1028                                         if (path.Length == 0)
1029                                                 continue;
1030
1031                                         AddPathToIgnore (path);
1032                                 }
1033                         }
1034                 }
1035
1036                 static void OnEntryRemoved (string vp)
1037                 {
1038                         BuildManagerRemoveEntryEventHandler eh = events [buildManagerRemoveEntryEvent] as BuildManagerRemoveEntryEventHandler;
1039
1040                         if (eh != null)
1041                                 eh (new BuildManagerRemoveEntryEventArgs (vp, HttpContext.Current));
1042                 }
1043                 
1044                 static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason)
1045                 {
1046                         string virtualPath;
1047
1048                         if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX))
1049                                 virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH);
1050                         else
1051                                 return;
1052                         
1053                         bool locked = false;
1054                         try {
1055 #if SYSTEMCORE_DEP
1056                                 buildCacheLock.EnterWriteLock ();
1057 #else
1058                                 buildCacheLock.AcquireWriterLock (-1);
1059 #endif
1060                                 locked = true;
1061
1062                                 if (HasCachedItemNoLock (virtualPath)) {
1063                                         buildCache.Remove (virtualPath);
1064                                         OnEntryRemoved (virtualPath);
1065                                 }
1066                         } finally {
1067                                 if (locked) {
1068 #if SYSTEMCORE_DEP
1069                                         buildCacheLock.ExitWriteLock ();
1070 #else
1071                                         buildCacheLock.ReleaseWriterLock ();
1072 #endif
1073                                 }
1074                         }
1075                 }
1076                 
1077                 static void ReferenceAssemblyInCompilation (BuildManagerCacheItem bmci)
1078                 {
1079                         if (recursionDepth == 0 || referencedAssemblies.Contains (bmci.BuiltAssembly))
1080                                 return;
1081
1082                         referencedAssemblies.Add (bmci.BuiltAssembly);
1083                 }
1084                 
1085                 static void RemoveFailedAssemblies (string requestedVirtualPath, CompilationException ex, AssemblyBuilder abuilder,
1086                                                     BuildProviderGroup group, CompilerResults results, bool debug)
1087                 {
1088                         StringBuilder sb;
1089                         string newline;
1090                         
1091                         if (debug) {
1092                                 newline = Environment.NewLine;
1093                                 sb = new StringBuilder ("Compilation of certain files in a batch failed. Another attempt to compile the batch will be made." + newline);
1094                                 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
1095                         } else {
1096                                 newline = null;
1097                                 sb = null;
1098                         }
1099                         
1100                         var failedBuildProviders = new List <BuildProvider> ();
1101                         BuildProvider bp;
1102                         HttpContext ctx = HttpContext.Current;
1103                         HttpRequest req = ctx != null ? ctx.Request : null;
1104                         bool rethrow = false;
1105                         
1106                         foreach (CompilerError error in results.Errors) {
1107                                 if (error.IsWarning)
1108                                         continue;
1109                                 
1110                                 bp = abuilder.GetBuildProviderForPhysicalFilePath (error.FileName);
1111                                 if (bp == null) {
1112                                         bp = FindBuildProviderForPhysicalPath (error.FileName, group, req);
1113                                         if (bp == null)
1114                                                 continue;
1115                                 }
1116
1117                                 if (String.Compare (bp.VirtualPath, requestedVirtualPath, StringComparison.Ordinal) == 0)
1118                                         rethrow = true;
1119
1120                                 if (!failedBuildProviders.Contains (bp)) {
1121                                         failedBuildProviders.Add (bp);
1122                                         if (sb != null)
1123                                                 sb.AppendFormat ("\t{0}{1}", bp.VirtualPath, newline);
1124                                 }
1125
1126                                 if (sb != null)
1127                                         sb.AppendFormat ("\t\t{0}{1}", error, newline);
1128                         }
1129
1130                         foreach (BuildProvider fbp in failedBuildProviders)
1131                                 group.Remove (fbp);
1132                         
1133                         if (sb != null) {
1134                                 sb.AppendFormat ("{0}The following exception has been thrown for the file(s) listed above:{0}{1}",
1135                                                  newline, ex.ToString ());
1136                                 ShowDebugModeMessage (sb.ToString ());
1137                                 sb = null;
1138                         }
1139
1140                         if (rethrow)
1141                                 throw new HttpException ("Compilation failed.", ex);
1142                 }
1143                 
1144                 static void SetCommonParameters (CompilationSection config, CompilerParameters p, Type compilerType, string language)
1145                 {
1146                         p.IncludeDebugInformation = config.Debug;
1147                         MonoSettingsSection mss = WebConfigurationManager.GetSection ("system.web/monoSettings") as MonoSettingsSection;
1148                         if (mss == null || !mss.UseCompilersCompatibility)
1149                                 return;
1150
1151                         Compiler compiler = mss.CompilersCompatibility.Get (language);
1152                         if (compiler == null)
1153                                 return;
1154
1155                         Type type = HttpApplication.LoadType (compiler.Type, false);
1156                         if (type != compilerType)
1157                                 return;
1158
1159                         p.CompilerOptions = String.Concat (p.CompilerOptions, " ", compiler.CompilerOptions);
1160                 }
1161
1162                 static void ShowDebugModeMessage (string msg)
1163                 {
1164                         if (suppressDebugModeMessages)
1165                                 return;
1166                         
1167                         Console.WriteLine ();
1168                         Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1169                         Console.WriteLine (msg);
1170                         Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1171                         Console.WriteLine ();
1172                 }
1173
1174                 static void StoreInCache (BuildProvider bp, Assembly compiledAssembly, CompilerResults results)
1175                 {
1176                         buildCache.Add (bp.VirtualPath, new BuildManagerCacheItem (compiledAssembly, bp, results));
1177                         
1178                         HttpContext ctx = HttpContext.Current;
1179                         HttpRequest req = ctx != null ? ctx.Request : null;
1180                         CacheDependency dep;
1181                         string virtualPath = bp.VirtualPath;
1182                         
1183                         if (req != null) {
1184                                 IDictionary <string, bool> deps = bp.ExtractDependencies ();
1185                                 var files = new List <string> ();
1186                                 string physicalPath;
1187
1188                                 physicalPath = req.MapPath (virtualPath);
1189                                 if (File.Exists (physicalPath))
1190                                         files.Add (physicalPath);
1191                                 
1192                                 if (deps != null && deps.Count > 0) {
1193                                         foreach (var d in deps) {
1194                                                 physicalPath = req.MapPath (d.Key);
1195                                                 if (!File.Exists (physicalPath))
1196                                                         continue;
1197                                                 if (!files.Contains (physicalPath))
1198                                                         files.Add (physicalPath);
1199                                         }
1200                                 }
1201
1202                                 dep = new CacheDependency (files.ToArray ());
1203                         } else
1204                                 dep = null;
1205
1206                         HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath,
1207                                                        true,
1208                                                        dep,
1209                                                        Cache.NoAbsoluteExpiration,
1210                                                        Cache.NoSlidingExpiration,
1211                                                        CacheItemPriority.High,
1212                                                        new CacheItemRemovedCallback (OnVirtualPathChanged));
1213                 }
1214         }
1215 }
1216