2 // System.Web.Compilation.BuildManager
5 // Chris Toshok (toshok@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@novell.com)
7 // Marek Habersack (mhabersack@novell.com)
9 // (C) 2006-2009 Novell, Inc (http://www.novell.com)
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
35 using System.CodeDom.Compiler;
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.Collections.Specialized;
39 using System.ComponentModel;
41 using System.Reflection;
43 using System.Threading;
46 using System.Web.Caching;
47 using System.Web.Configuration;
48 using System.Web.Hosting;
49 using System.Web.Util;
51 using System.Runtime.Versioning;
54 namespace System.Web.Compilation
56 public sealed class BuildManager
58 internal const string FAKE_VIRTUAL_PATH_PREFIX = "/@@MonoFakeVirtualPath@@";
59 const string BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX = "@@Build_Manager@@";
60 static int BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH = BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX.Length;
62 static readonly object bigCompilationLock = new object ();
63 static readonly object virtualPathsToIgnoreLock = new object ();
64 static readonly char[] virtualPathsToIgnoreSplitChars = {','};
66 static EventHandlerList events = new EventHandlerList ();
67 static object buildManagerRemoveEntryEvent = new object ();
70 static Dictionary <string, bool> virtualPathsToIgnore;
71 static bool virtualPathsToIgnoreChecked;
72 static bool haveVirtualPathsToIgnore;
73 static List <Assembly> AppCode_Assemblies = new List<Assembly>();
74 static List <Assembly> TopLevel_Assemblies = new List<Assembly>();
75 static Dictionary <Type, CodeDomProvider> codeDomProviders;
76 static Dictionary <string, BuildManagerCacheItem> buildCache;
77 static List <Assembly> referencedAssemblies;
78 static List <Assembly> configReferencedAssemblies;
79 static bool getReferencedAssembliesInvoked;
81 static int buildCount;
82 static bool is_precompiled;
83 static bool allowReferencedAssembliesCaching;
85 static List <Assembly> dynamicallyRegisteredAssemblies;
86 static bool? batchCompilationEnabled;
87 static FrameworkName targetFramework;
88 static bool preStartMethodsDone;
89 static bool preStartMethodsRunning;
91 //static bool updatable; unused
92 static Dictionary<string, PreCompilationData> precompiled;
94 // This is here _only_ for the purpose of unit tests!
95 internal static bool suppressDebugModeMessages;
98 static ReaderWriterLockSlim buildCacheLock;
100 static ReaderWriterLock buildCacheLock;
102 static ulong recursionDepth;
104 internal static bool AllowReferencedAssembliesCaching {
105 get { return allowReferencedAssembliesCaching; }
106 set { allowReferencedAssembliesCaching = value; }
109 internal static bool IsPrecompiled {
110 get { return is_precompiled; }
113 internal static event BuildManagerRemoveEntryEventHandler RemoveEntry {
114 add { events.AddHandler (buildManagerRemoveEntryEvent, value); }
115 remove { events.RemoveHandler (buildManagerRemoveEntryEvent, value); }
119 internal static bool PreStartMethodsRunning {
120 get { return preStartMethodsRunning;
124 public static bool? BatchCompilationEnabled {
125 get { return batchCompilationEnabled; }
127 if (preStartMethodsDone)
128 throw new InvalidOperationException ("This method cannot be called after the application's pre-start initialization stage.");
129 batchCompilationEnabled = value;
133 public static FrameworkName TargetFramework {
135 if (targetFramework == null) {
136 CompilationSection cs = CompilationConfig;
141 framework = cs.TargetFramework;
143 if (String.IsNullOrEmpty (framework))
144 targetFramework = new FrameworkName (".NETFramework,Version=v4.0");
146 targetFramework = new FrameworkName (framework);
149 return targetFramework;
153 internal static bool BatchMode {
156 if (batchCompilationEnabled != null)
157 return (bool)batchCompilationEnabled;
160 return false; // Fix for bug #380985
162 CompilationSection cs = CompilationConfig;
170 // Assemblies built from the App_Code directory
171 public static IList CodeAssemblies {
172 get { return AppCode_Assemblies; }
175 internal static CompilationSection CompilationConfig {
176 get { return WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection; }
179 internal static bool HaveResources {
183 internal static IList TopLevelAssemblies {
184 get { return TopLevel_Assemblies; }
187 static BuildManager ()
189 hosted = (AppDomain.CurrentDomain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes";
190 buildCache = new Dictionary <string, BuildManagerCacheItem> (RuntimeHelpers.StringEqualityComparerCulture);
192 buildCacheLock = new ReaderWriterLockSlim ();
194 buildCacheLock = new ReaderWriterLock ();
196 referencedAssemblies = new List <Assembly> ();
199 string appPath = HttpRuntime.AppDomainAppPath;
200 string precomp_name = null;
201 is_precompiled = String.IsNullOrEmpty (appPath) ? false : File.Exists ((precomp_name = Path.Combine (appPath, "PrecompiledApp.config")));
203 is_precompiled = LoadPrecompilationInfo (precomp_name);
206 // Deal with precompiled sites deployed in a different virtual path
207 static void FixVirtualPaths ()
209 if (precompiled == null)
214 foreach (string vpath in precompiled.Keys) {
215 parts = vpath.Split ('/');
216 for (int i = 0; i < parts.Length; i++) {
217 if (String.IsNullOrEmpty (parts [i]))
219 // The path must be rooted, otherwise PhysicalPath returned
220 // below will be relative to the current request path and
221 // File.Exists will return a false negative. See bug #546053
222 string test_path = "/" + String.Join ("/", parts, i, parts.Length - i);
223 VirtualPath result = GetAbsoluteVirtualPath (test_path);
224 if (result != null && File.Exists (result.PhysicalPath)) {
231 string app_vpath = HttpRuntime.AppDomainAppVirtualPath;
232 if (skip == -1 || (skip == 0 && app_vpath == "/"))
235 if (!app_vpath.EndsWith ("/"))
236 app_vpath = app_vpath + "/";
237 Dictionary<string, PreCompilationData> copy = new Dictionary<string, PreCompilationData> (precompiled);
238 precompiled.Clear ();
239 foreach (KeyValuePair<string,PreCompilationData> entry in copy) {
240 parts = entry.Key.Split ('/');
242 if (String.IsNullOrEmpty (parts [0]))
243 new_path = app_vpath + String.Join ("/", parts, skip + 1, parts.Length - skip - 1);
245 new_path = app_vpath + String.Join ("/", parts, skip, parts.Length - skip);
246 entry.Value.VirtualPath = new_path;
247 precompiled.Add (new_path, entry.Value);
251 static bool LoadPrecompilationInfo (string precomp_config)
253 using (XmlTextReader reader = new XmlTextReader (precomp_config)) {
254 reader.MoveToContent ();
255 if (reader.Name != "precompiledApp")
259 if (reader.HasAttributes)
260 while (reader.MoveToNextAttribute ())
261 if (reader.Name == "updatable") {
262 updatable = (reader.Value == "true");
268 string [] compiled = Directory.GetFiles (HttpRuntime.BinDirectory, "*.compiled");
269 foreach (string str in compiled)
276 static void LoadCompiled (string filename)
278 using (XmlTextReader reader = new XmlTextReader (filename)) {
279 reader.MoveToContent ();
280 if (reader.Name == "preserve" && reader.HasAttributes) {
281 reader.MoveToNextAttribute ();
282 string val = reader.Value;
283 // 1 -> app_code subfolder - add the assembly to CodeAssemblies
286 // 6 -> app_code - add the assembly to CodeAssemblies
288 // 9 -> App_GlobalResources - set the assembly for HttpContext
289 if (reader.Name == "resultType" && (val == "2" || val == "3" || val == "8"))
290 LoadPageData (reader, true);
291 else if (val == "1" || val == "6") {
292 PreCompilationData pd = LoadPageData (reader, false);
293 CodeAssemblies.Add (Assembly.Load (pd.AssemblyFileName));
294 } else if (val == "9") {
295 PreCompilationData pd = LoadPageData (reader, false);
296 HttpContext.AppGlobalResourcesAssembly = Assembly.Load (pd.AssemblyFileName);
302 class PreCompilationData {
303 public string VirtualPath;
304 public string AssemblyFileName;
305 public string TypeName;
309 static PreCompilationData LoadPageData (XmlTextReader reader, bool store)
311 PreCompilationData pc_data = new PreCompilationData ();
313 while (reader.MoveToNextAttribute ()) {
314 string name = reader.Name;
315 if (name == "virtualPath")
316 pc_data.VirtualPath = VirtualPathUtility.RemoveTrailingSlash (reader.Value);
317 else if (name == "assembly")
318 pc_data.AssemblyFileName = reader.Value;
319 else if (name == "type")
320 pc_data.TypeName = reader.Value;
323 if (precompiled == null)
324 precompiled = new Dictionary<string, PreCompilationData> (RuntimeHelpers.StringEqualityComparerCulture);
325 precompiled.Add (pc_data.VirtualPath, pc_data);
330 static void AddAssembly (Assembly asm, List <Assembly> al)
332 if (al.Contains (asm))
338 static void AddPathToIgnore (string vp)
340 if (virtualPathsToIgnore == null)
341 virtualPathsToIgnore = new Dictionary <string, bool> (RuntimeHelpers.StringEqualityComparerCulture);
343 VirtualPath path = GetAbsoluteVirtualPath (vp);
344 string vpAbsolute = path.Absolute;
345 if (!virtualPathsToIgnore.ContainsKey (vpAbsolute)) {
346 virtualPathsToIgnore.Add (vpAbsolute, true);
347 haveVirtualPathsToIgnore = true;
350 string vpRelative = path.AppRelative;
351 if (!virtualPathsToIgnore.ContainsKey (vpRelative)) {
352 virtualPathsToIgnore.Add (vpRelative, true);
353 haveVirtualPathsToIgnore = true;
356 if (!virtualPathsToIgnore.ContainsKey (vp)) {
357 virtualPathsToIgnore.Add (vp, true);
358 haveVirtualPathsToIgnore = true;
362 internal static void AddToReferencedAssemblies (Assembly asm)
364 // should not be used
367 static void AssertVirtualPathExists (VirtualPath virtualPath)
370 bool dothrow = false;
372 if (virtualPath.IsFake) {
373 realpath = virtualPath.PhysicalPath;
374 if (!File.Exists (realpath) && !Directory.Exists (realpath))
377 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
378 string vpAbsolute = virtualPath.Absolute;
380 if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute))
385 throw new HttpException (404, "The file '" + virtualPath + "' does not exist.", virtualPath.Absolute);
388 static void Build (VirtualPath vp)
390 AssertVirtualPathExists (vp);
392 CompilationSection cs = CompilationConfig;
393 lock (bigCompilationLock) {
395 if (HasCachedItemNoLock (vp.Absolute, out entryExists))
398 if (recursionDepth == 0)
399 referencedAssemblies.Clear ();
403 BuildInner (vp, cs != null ? cs.Debug : false);
404 if (entryExists && recursionDepth <= 1)
405 // We count only update builds - first time a file
406 // (or a batch) is built doesn't count.
409 // See http://support.microsoft.com/kb/319947
410 if (buildCount > cs.NumRecompilesBeforeAppRestart)
411 HttpRuntime.UnloadAppDomain ();
417 // This method assumes it is being called with the big compilation lock held
418 static void BuildInner (VirtualPath vp, bool debug)
420 var builder = new BuildManagerDirectoryBuilder (vp);
421 bool recursive = recursionDepth > 1;
422 List <BuildProviderGroup> builderGroups = builder.Build (IsSingleBuild (vp, recursive));
423 if (builderGroups == null)
426 string vpabsolute = vp.Absolute;
427 int buildHash = (vpabsolute.GetHashCode () | (int)DateTime.Now.Ticks) + (int)recursionDepth;
428 string assemblyBaseName;
429 AssemblyBuilder abuilder;
432 bool singleBuild, needMainVpBuild;
433 CompilationException compilationError;
435 // Each group becomes a separate assembly.
436 foreach (BuildProviderGroup group in builderGroups) {
437 needMainVpBuild = false;
438 compilationError = null;
439 assemblyBaseName = null;
441 if (group.Count == 1) {
442 if (recursive || !group.Master)
443 assemblyBaseName = String.Format ("{0}_{1}.{2:x}.", group.NamePrefix, VirtualPathUtility.GetFileName (group [0].VirtualPath), buildHash);
448 if (assemblyBaseName == null)
449 assemblyBaseName = group.NamePrefix + "_";
451 ct = group.CompilerType;
453 while (attempts > 0) {
454 abuilder = new AssemblyBuilder (vp, CreateDomProvider (ct), assemblyBaseName);
455 abuilder.CompilerOptions = ct.CompilerParameters;
456 abuilder.AddAssemblyReference (GetReferencedAssemblies () as List <Assembly>);
458 GenerateAssembly (abuilder, group, vp, debug);
460 } catch (CompilationException ex) {
463 throw new HttpException ("Single file build failed.", ex);
466 needMainVpBuild = true;
467 compilationError = ex;
471 CompilerResults results = ex.Results;
473 throw new HttpException ("No results returned from failed compilation.", ex);
475 RemoveFailedAssemblies (vpabsolute, ex, abuilder, group, results, debug);
479 if (needMainVpBuild) {
480 // One last attempt - try to build just the requested path
481 // if it's not built yet or just return without throwing the
482 // exception if it has already been built.
483 if (HasCachedItemNoLock (vpabsolute)) {
485 DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
486 compilationError, vpabsolute);
490 // This will trigger a recursive build of the requested vp,
491 // which means only the vp alone will be built (or not);
493 if (HasCachedItemNoLock (vpabsolute)) {
495 DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:",
496 compilationError, vpabsolute);
500 // In theory this code is unreachable. If the recursive
501 // build of the main vp failed, then it should have thrown
502 // the build exception.
503 throw new HttpException ("Requested virtual path build failed.", compilationError);
508 static CodeDomProvider CreateDomProvider (CompilerType ct)
510 if (codeDomProviders == null)
511 codeDomProviders = new Dictionary <Type, CodeDomProvider> ();
513 Type type = ct.CodeDomProviderType;
515 CompilationSection cs = CompilationConfig;
516 CompilerType tmp = GetDefaultCompilerTypeForLanguage (cs.DefaultLanguage, cs);
518 type = tmp.CodeDomProviderType;
525 if (codeDomProviders.TryGetValue (type, out ret))
528 ret = Activator.CreateInstance (type) as CodeDomProvider;
532 codeDomProviders.Add (type, ret);
536 internal static void CallPreStartMethods ()
538 if (preStartMethodsDone)
541 preStartMethodsRunning = true;
542 MethodInfo mi = null;
544 List <MethodInfo> methods = LoadPreStartMethodsFromAssemblies (GetReferencedAssemblies () as List <Assembly>);
545 if (methods == null || methods.Count == 0)
548 foreach (MethodInfo m in methods) {
550 m.Invoke (null, null);
552 } catch (Exception ex) {
553 throw new HttpException (
554 String.Format ("The pre-application start initialization method {0} on type {1} threw an exception with the following error message: {2}",
555 mi != null ? mi.Name : "UNKNOWN",
556 mi != null ? mi.DeclaringType.FullName : "UNKNOWN",
561 preStartMethodsRunning = false;
562 preStartMethodsDone = true;
566 static List <MethodInfo> LoadPreStartMethodsFromAssemblies (List <Assembly> assemblies)
568 if (assemblies == null || assemblies.Count == 0)
571 var ret = new List <MethodInfo> ();
574 PreApplicationStartMethodAttribute attr;
576 foreach (Assembly asm in assemblies) {
578 attributes = asm.GetCustomAttributes (typeof (PreApplicationStartMethodAttribute), false);
579 if (attributes == null || attributes.Length == 0)
582 attr = attributes [0] as PreApplicationStartMethodAttribute;
591 Exception error = null;
594 mi = type.GetMethod (attr.MethodName, BindingFlags.Static | BindingFlags.Public, null, new Type[] {}, null);
597 } catch (Exception ex) {
603 throw new HttpException (
605 "The method specified by the PreApplicationStartMethodAttribute on assembly '{0}' cannot be resolved. Type: '{1}', MethodName: '{2}'. Verify that the type is public and the method is public and static (Shared in Visual Basic).",
618 public static Type GetGlobalAsaxType ()
620 Type ret = HttpApplicationFactory.AppType;
621 if (!preStartMethodsRunning)
622 throw new InvalidOperationException ("This method cannot be called during the application's pre-start initialization stage.");
627 public static Stream CreateCachedFile (string fileName)
629 if (fileName != null && (fileName == String.Empty || fileName.IndexOf (Path.DirectorySeparatorChar) != -1))
630 throw new ArgumentException ("Value does not fall within the expected range.");
632 string path = Path.Combine (HttpRuntime.CodegenDir, fileName);
633 return new FileStream (path, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
636 public static Stream ReadCachedFile (string fileName)
638 if (fileName != null && (fileName == String.Empty || fileName.IndexOf (Path.DirectorySeparatorChar) != -1))
639 throw new ArgumentException ("Value does not fall within the expected range.");
641 string path = Path.Combine (HttpRuntime.CodegenDir, fileName);
642 if (!File.Exists (path))
645 return new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.None);
648 [MonoDocumentationNote ("Fully implemented but no info on application pre-init stage is available yet.")]
649 public static void AddReferencedAssembly (Assembly assembly)
651 if (assembly == null)
652 throw new ArgumentNullException ("assembly");
654 Type ret = HttpApplicationFactory.AppType;
655 if (preStartMethodsDone)
656 throw new InvalidOperationException ("This method cannot be called after the application's pre-start initialization stage.");
658 if (dynamicallyRegisteredAssemblies == null)
659 dynamicallyRegisteredAssemblies = new List <Assembly> ();
661 if (!dynamicallyRegisteredAssemblies.Contains (assembly))
662 dynamicallyRegisteredAssemblies.Add (assembly);
665 [MonoTODO ("A no-op until we use IWebObjectFactory internally. Always returns null.")]
666 public static IWebObjectFactory GetObjectFactory (string virtualPath, bool throwIfNotFound)
671 public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType)
673 return CreateInstanceFromVirtualPath (GetAbsoluteVirtualPath (virtualPath), requiredBaseType);
676 internal static object CreateInstanceFromVirtualPath (VirtualPath virtualPath, Type requiredBaseType)
678 if (requiredBaseType == null)
679 throw new NullReferenceException (); // This is what MS does, but
680 // from somewhere else.
682 Type type = GetCompiledType (virtualPath);
686 if (!requiredBaseType.IsAssignableFrom (type))
687 throw new HttpException (500,
688 String.Format ("Type '{0}' does not inherit from '{1}'.", type.FullName, requiredBaseType.FullName));
690 return Activator.CreateInstance (type, null);
693 static void DescribeCompilationError (string format, CompilationException ex, params object[] parms)
695 StringBuilder sb = new StringBuilder ();
696 string newline = Environment.NewLine;
699 sb.AppendFormat (format + newline, parms);
701 sb.Append (format + newline);
703 CompilerResults results = ex != null ? ex.Results : null;
705 sb.Append ("No compiler error information present." + newline);
707 sb.Append ("Compiler errors:" + newline);
708 foreach (CompilerError error in results.Errors)
709 sb.Append (" " + error.ToString () + newline);
713 sb.Append (newline + "Exception thrown:" + newline);
714 sb.Append (ex.ToString ());
717 ShowDebugModeMessage (sb.ToString ());
720 static BuildProvider FindBuildProviderForPhysicalPath (string path, BuildProviderGroup group, HttpRequest req)
722 if (req == null || String.IsNullOrEmpty (path))
725 foreach (BuildProvider bp in group) {
726 if (String.Compare (path, req.MapPath (bp.VirtualPath), RuntimeHelpers.StringComparison) == 0)
733 static void GenerateAssembly (AssemblyBuilder abuilder, BuildProviderGroup group, VirtualPath vp, bool debug)
735 IDictionary <string, bool> deps;
736 BuildManagerCacheItem bmci;
737 string bvp, vpabsolute = vp.Absolute;
743 newline = Environment.NewLine;
744 sb = new StringBuilder ("Code generation for certain virtual paths in a batch failed. Those files have been removed from the batch." + newline);
745 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
751 List <BuildProvider> failedBuildProviders = null;
752 StringComparison stringComparison = RuntimeHelpers.StringComparison;
753 foreach (BuildProvider bp in group) {
754 bvp = bp.VirtualPath;
755 if (HasCachedItemNoLock (bvp))
759 bp.GenerateCode (abuilder);
760 } catch (Exception ex) {
761 if (String.Compare (bvp, vpabsolute, stringComparison) == 0) {
762 if (ex is CompilationException || ex is ParseException)
765 throw new HttpException ("Code generation failed.", ex);
768 if (failedBuildProviders == null)
769 failedBuildProviders = new List <BuildProvider> ();
770 failedBuildProviders.Add (bp);
776 sb.AppendFormat ("Failed file virtual path: {0}; Exception: {1}{2}{1}", bp.VirtualPath, newline, ex);
781 deps = bp.ExtractDependencies ();
783 foreach (var dep in deps) {
784 bmci = GetCachedItemNoLock (dep.Key);
785 if (bmci == null || bmci.BuiltAssembly == null)
787 abuilder.AddAssemblyReference (bmci.BuiltAssembly);
792 if (sb != null && failedCount > 0)
793 ShowDebugModeMessage (sb.ToString ());
795 if (failedBuildProviders != null) {
796 foreach (BuildProvider bp in failedBuildProviders)
800 foreach (Assembly asm in referencedAssemblies) {
804 abuilder.AddAssemblyReference (asm);
807 CompilerResults results = abuilder.BuildAssembly (vp);
809 // No results is not an error - it is possible that the assembly builder contained only .asmx and
810 // .ashx files which had no body, just the directive. In such case, no code unit or code file is added
811 // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
812 // to be added to the cache.
813 Assembly compiledAssembly = results != null ? results.CompiledAssembly : null;
817 buildCacheLock.EnterWriteLock ();
819 buildCacheLock.AcquireWriterLock (-1);
822 if (compiledAssembly != null)
823 referencedAssemblies.Add (compiledAssembly);
825 foreach (BuildProvider bp in group) {
826 if (HasCachedItemNoLock (bp.VirtualPath))
829 StoreInCache (bp, compiledAssembly, results);
834 buildCacheLock.ExitWriteLock ();
836 buildCacheLock.ReleaseWriterLock ();
842 static VirtualPath GetAbsoluteVirtualPath (string virtualPath)
846 if (!VirtualPathUtility.IsRooted (virtualPath)) {
847 HttpContext ctx = HttpContext.Current;
848 HttpRequest req = ctx != null ? ctx.Request : null;
851 string fileDir = req.FilePath;
852 if (!String.IsNullOrEmpty (fileDir) && String.Compare (fileDir, "/", StringComparison.Ordinal) != 0)
853 fileDir = VirtualPathUtility.GetDirectory (fileDir);
857 vp = VirtualPathUtility.Combine (fileDir, virtualPath);
859 throw new HttpException ("No context, cannot map paths.");
863 return new VirtualPath (vp);
866 [MonoTODO ("Not implemented, always returns null")]
867 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath)
869 return null; // null is ok here until we store the dependency set in the Cache.
872 [MonoTODO ("Not implemented, always returns null")]
873 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath, bool ensureIsUpToDate)
875 return null; // null is ok here until we store the dependency set in the Cache.
878 static BuildManagerCacheItem GetCachedItem (string vp)
884 buildCacheLock.EnterReadLock ();
886 buildCacheLock.AcquireReaderLock (-1);
889 return GetCachedItemNoLock (vp);
893 buildCacheLock.ExitReadLock ();
895 buildCacheLock.ReleaseReaderLock ();
901 static BuildManagerCacheItem GetCachedItemNoLock (string vp)
903 BuildManagerCacheItem ret;
904 if (buildCache.TryGetValue (vp, out ret))
910 internal static Type GetCodeDomProviderType (BuildProvider provider)
912 CompilerType codeCompilerType;
913 Type codeDomProviderType = null;
915 codeCompilerType = provider.CodeCompilerType;
916 if (codeCompilerType != null)
917 codeDomProviderType = codeCompilerType.CodeDomProviderType;
919 if (codeDomProviderType == null)
920 throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type."));
922 return codeDomProviderType;
925 static Type GetPrecompiledType (string virtualPath)
927 PreCompilationData pc_data;
928 if (precompiled != null && precompiled.TryGetValue (virtualPath, out pc_data)) {
929 if (pc_data.Type == null) {
930 pc_data.Type = Type.GetType (pc_data.TypeName + ", " + pc_data.AssemblyFileName, true);
937 internal static Type GetPrecompiledApplicationType ()
942 Type apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath, "Global.asax"));
944 apptype = GetPrecompiledType (VirtualPathUtility.Combine (HttpRuntime.AppDomainAppVirtualPath , "global.asax"));
948 public static Assembly GetCompiledAssembly (string virtualPath)
950 return GetCompiledAssembly (GetAbsoluteVirtualPath (virtualPath));
953 internal static Assembly GetCompiledAssembly (VirtualPath virtualPath)
955 string vpabsolute = virtualPath.Absolute;
956 if (is_precompiled) {
957 Type type = GetPrecompiledType (vpabsolute);
959 return type.Assembly;
961 BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
963 return bmci.BuiltAssembly;
966 bmci = GetCachedItem (vpabsolute);
968 return bmci.BuiltAssembly;
973 public static Type GetCompiledType (string virtualPath)
975 return GetCompiledType (GetAbsoluteVirtualPath (virtualPath));
978 internal static Type GetCompiledType (VirtualPath virtualPath)
980 string vpabsolute = virtualPath.Absolute;
981 if (is_precompiled) {
982 Type type = GetPrecompiledType (vpabsolute);
986 BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
988 ReferenceAssemblyInCompilation (bmci);
993 bmci = GetCachedItem (vpabsolute);
995 ReferenceAssemblyInCompilation (bmci);
1002 public static string GetCompiledCustomString (string virtualPath)
1004 return GetCompiledCustomString (GetAbsoluteVirtualPath (virtualPath));
1007 internal static string GetCompiledCustomString (VirtualPath virtualPath)
1009 string vpabsolute = virtualPath.Absolute;
1010 BuildManagerCacheItem bmci = GetCachedItem (vpabsolute);
1012 return bmci.CompiledCustomString;
1014 Build (virtualPath);
1015 bmci = GetCachedItem (vpabsolute);
1017 return bmci.CompiledCustomString;
1022 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection)
1024 return GetDefaultCompilerTypeForLanguage (language, configSection, true);
1027 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection, bool throwOnMissing)
1029 // MS throws when accesing a Hashtable, we do here.
1030 if (language == null || language.Length == 0)
1031 throw new ArgumentNullException ("language");
1033 CompilationSection config;
1034 if (configSection == null)
1035 config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
1037 config = configSection;
1039 Compiler compiler = config.Compilers.Get (language);
1040 CompilerParameters p;
1043 if (compiler != null) {
1044 type = HttpApplication.LoadType (compiler.Type, true);
1045 p = new CompilerParameters ();
1046 p.CompilerOptions = compiler.CompilerOptions;
1047 p.WarningLevel = compiler.WarningLevel;
1048 SetCommonParameters (config, p, type, language);
1049 return new CompilerType (type, p);
1052 if (CodeDomProvider.IsDefinedLanguage (language)) {
1053 CompilerInfo info = CodeDomProvider.GetCompilerInfo (language);
1054 p = info.CreateDefaultCompilerParameters ();
1055 type = info.CodeDomProviderType;
1056 SetCommonParameters (config, p, type, language);
1057 return new CompilerType (type, p);
1061 throw new HttpException (String.Concat ("No compiler for language '", language, "'."));
1066 public static ICollection GetReferencedAssemblies ()
1068 if (getReferencedAssembliesInvoked)
1069 return configReferencedAssemblies;
1071 if (allowReferencedAssembliesCaching)
1072 getReferencedAssembliesInvoked = true;
1074 if (configReferencedAssemblies == null)
1075 configReferencedAssemblies = new List <Assembly> ();
1076 else if (getReferencedAssembliesInvoked)
1077 configReferencedAssemblies.Clear ();
1079 CompilationSection compConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
1080 if (compConfig == null)
1081 return configReferencedAssemblies;
1083 bool addAssembliesInBin = false;
1084 foreach (AssemblyInfo info in compConfig.Assemblies) {
1085 if (info.Assembly == "*")
1086 addAssembliesInBin = is_precompiled ? false : true;
1088 LoadAssembly (info, configReferencedAssemblies);
1091 foreach (Assembly topLevelAssembly in TopLevelAssemblies)
1092 configReferencedAssemblies.Add (topLevelAssembly);
1094 foreach (string assLocation in WebConfigurationManager.ExtraAssemblies)
1095 LoadAssembly (assLocation, configReferencedAssemblies);
1097 if (dynamicallyRegisteredAssemblies != null)
1098 foreach (Assembly registeredAssembly in dynamicallyRegisteredAssemblies)
1099 configReferencedAssemblies.Add (registeredAssembly);
1101 // Precompiled sites unconditionally load all assemblies from bin/ (fix for
1103 if (is_precompiled || addAssembliesInBin) {
1104 foreach (string s in HttpApplication.BinDirectoryAssemblies) {
1106 LoadAssembly (s, configReferencedAssemblies);
1107 } catch (BadImageFormatException) {
1113 return configReferencedAssemblies;
1116 // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers
1117 public static Type GetType (string typeName, bool throwOnError)
1119 return GetType (typeName, throwOnError, false);
1122 public static Type GetType (string typeName, bool throwOnError, bool ignoreCase)
1126 foreach (Assembly asm in TopLevel_Assemblies) {
1127 ret = asm.GetType (typeName, throwOnError, ignoreCase);
1131 } catch (Exception ex) {
1132 throw new HttpException ("Failed to find the specified type.", ex);
1137 public static ICollection GetVirtualPathDependencies (string virtualPath)
1139 return GetVirtualPathDependencies (virtualPath, null);
1142 internal static ICollection GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider)
1144 BuildProvider provider = bprovider;
1145 if (provider == null) {
1146 CompilationSection cs = CompilationConfig;
1149 provider = BuildManagerDirectoryBuilder.GetBuildProvider (virtualPath, cs.BuildProviders);
1152 if (provider == null)
1155 IDictionary <string, bool> deps = provider.ExtractDependencies ();
1159 return (ICollection)deps.Keys;
1162 internal static bool HasCachedItemNoLock (string vp, out bool entryExists)
1164 BuildManagerCacheItem item;
1166 if (buildCache.TryGetValue (vp, out item)) {
1168 return item != null;
1171 entryExists = false;
1175 internal static bool HasCachedItemNoLock (string vp)
1178 return HasCachedItemNoLock (vp, out dummy);
1181 internal static bool IgnoreVirtualPath (string virtualPath)
1183 if (!virtualPathsToIgnoreChecked) {
1184 lock (virtualPathsToIgnoreLock) {
1185 if (!virtualPathsToIgnoreChecked)
1186 LoadVirtualPathsToIgnore ();
1187 virtualPathsToIgnoreChecked = true;
1191 if (!haveVirtualPathsToIgnore)
1194 if (virtualPathsToIgnore.ContainsKey (virtualPath))
1200 static bool IsSingleBuild (VirtualPath vp, bool recursive)
1202 if (String.Compare (vp.AppRelative, "~/global.asax", StringComparison.OrdinalIgnoreCase) == 0)
1211 static void LoadAssembly (string path, List <Assembly> al)
1213 AddAssembly (Assembly.LoadFrom (path), al);
1216 static void LoadAssembly (AssemblyInfo info, List <Assembly> al)
1218 AddAssembly (Assembly.Load (info.Assembly), al);
1221 static void LoadVirtualPathsToIgnore ()
1223 NameValueCollection appSettings = WebConfigurationManager.AppSettings;
1224 if (appSettings == null)
1227 string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"];
1228 string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"];
1230 if (!String.IsNullOrEmpty (pathsFromConfig)) {
1231 string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars);
1234 foreach (string p in paths) {
1236 if (path.Length == 0)
1239 AddPathToIgnore (path);
1243 if (!String.IsNullOrEmpty (pathsFromFile)) {
1245 HttpContext ctx = HttpContext.Current;
1246 HttpRequest req = ctx != null ? ctx.Request : null;
1249 throw new HttpException ("Missing context, cannot continue.");
1251 realpath = req.MapPath (pathsFromFile);
1252 if (!File.Exists (realpath))
1255 string[] paths = File.ReadAllLines (realpath);
1256 if (paths == null || paths.Length == 0)
1260 foreach (string p in paths) {
1262 if (path.Length == 0)
1265 AddPathToIgnore (path);
1270 static void OnEntryRemoved (string vp)
1272 BuildManagerRemoveEntryEventHandler eh = events [buildManagerRemoveEntryEvent] as BuildManagerRemoveEntryEventHandler;
1275 eh (new BuildManagerRemoveEntryEventArgs (vp, HttpContext.Current));
1278 static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason)
1282 if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX))
1283 virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH);
1287 bool locked = false;
1290 buildCacheLock.EnterWriteLock ();
1292 buildCacheLock.AcquireWriterLock (-1);
1296 if (HasCachedItemNoLock (virtualPath)) {
1297 buildCache [virtualPath] = null;
1298 OnEntryRemoved (virtualPath);
1303 buildCacheLock.ExitWriteLock ();
1305 buildCacheLock.ReleaseWriterLock ();
1311 static void ReferenceAssemblyInCompilation (BuildManagerCacheItem bmci)
1313 if (recursionDepth == 0 || referencedAssemblies.Contains (bmci.BuiltAssembly))
1316 referencedAssemblies.Add (bmci.BuiltAssembly);
1319 static void RemoveFailedAssemblies (string requestedVirtualPath, CompilationException ex, AssemblyBuilder abuilder,
1320 BuildProviderGroup group, CompilerResults results, bool debug)
1326 newline = Environment.NewLine;
1327 sb = new StringBuilder ("Compilation of certain files in a batch failed. Another attempt to compile the batch will be made." + newline);
1328 sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline);
1334 var failedBuildProviders = new List <BuildProvider> ();
1336 HttpContext ctx = HttpContext.Current;
1337 HttpRequest req = ctx != null ? ctx.Request : null;
1338 bool rethrow = false;
1340 foreach (CompilerError error in results.Errors) {
1341 if (error.IsWarning)
1344 bp = abuilder.GetBuildProviderForPhysicalFilePath (error.FileName);
1346 bp = FindBuildProviderForPhysicalPath (error.FileName, group, req);
1351 if (String.Compare (bp.VirtualPath, requestedVirtualPath, StringComparison.Ordinal) == 0)
1354 if (!failedBuildProviders.Contains (bp)) {
1355 failedBuildProviders.Add (bp);
1357 sb.AppendFormat ("\t{0}{1}", bp.VirtualPath, newline);
1361 sb.AppendFormat ("\t\t{0}{1}", error, newline);
1364 foreach (BuildProvider fbp in failedBuildProviders)
1368 sb.AppendFormat ("{0}The following exception has been thrown for the file(s) listed above:{0}{1}",
1369 newline, ex.ToString ());
1370 ShowDebugModeMessage (sb.ToString ());
1375 throw new HttpException ("Compilation failed.", ex);
1378 static void SetCommonParameters (CompilationSection config, CompilerParameters p, Type compilerType, string language)
1380 p.IncludeDebugInformation = config.Debug;
1381 MonoSettingsSection mss = WebConfigurationManager.GetSection ("system.web/monoSettings") as MonoSettingsSection;
1382 if (mss == null || !mss.UseCompilersCompatibility)
1385 Compiler compiler = mss.CompilersCompatibility.Get (language);
1386 if (compiler == null)
1389 Type type = HttpApplication.LoadType (compiler.Type, false);
1390 if (type != compilerType)
1393 p.CompilerOptions = String.Concat (p.CompilerOptions, " ", compiler.CompilerOptions);
1396 static void ShowDebugModeMessage (string msg)
1398 if (suppressDebugModeMessages)
1401 Console.WriteLine ();
1402 Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1403 Console.WriteLine (msg);
1404 Console.WriteLine ("******* DEBUG MODE MESSAGE *******");
1405 Console.WriteLine ();
1408 static void StoreInCache (BuildProvider bp, Assembly compiledAssembly, CompilerResults results)
1410 string virtualPath = bp.VirtualPath;
1411 var item = new BuildManagerCacheItem (compiledAssembly, bp, results);
1413 if (buildCache.ContainsKey (virtualPath))
1414 buildCache [virtualPath] = item;
1416 buildCache.Add (virtualPath, item);
1418 HttpContext ctx = HttpContext.Current;
1419 HttpRequest req = ctx != null ? ctx.Request : null;
1420 CacheDependency dep;
1423 IDictionary <string, bool> deps = bp.ExtractDependencies ();
1424 var files = new List <string> ();
1425 string physicalPath;
1427 physicalPath = req.MapPath (virtualPath);
1428 if (File.Exists (physicalPath))
1429 files.Add (physicalPath);
1431 if (deps != null && deps.Count > 0) {
1432 foreach (var d in deps) {
1433 physicalPath = req.MapPath (d.Key);
1434 if (!File.Exists (physicalPath))
1436 if (!files.Contains (physicalPath))
1437 files.Add (physicalPath);
1441 dep = new CacheDependency (files.ToArray ());
1445 HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath,
1448 Cache.NoAbsoluteExpiration,
1449 Cache.NoSlidingExpiration,
1450 CacheItemPriority.High,
1451 new CacheItemRemovedCallback (OnVirtualPathChanged));