// // System.Web.Compilation.BuildManager // // Authors: // Chris Toshok (toshok@ximian.com) // Gonzalo Paniagua Javier (gonzalo@novell.com) // Marek Habersack (mhabersack@novell.com) // // (C) 2006-2009 Novell, Inc (http://www.novell.com) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.IO; using System.Reflection; using System.Text; using System.Threading; using System.Xml; using System.Web; using System.Web.Caching; using System.Web.Configuration; using System.Web.Hosting; using System.Web.Util; using System.Runtime.Versioning; namespace System.Web.Compilation { public sealed class BuildManager { internal const string FAKE_VIRTUAL_PATH_PREFIX = "/@@MonoFakeVirtualPath@@"; const string BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX = "@@Build_Manager@@"; static int BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH = BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX.Length; static readonly object bigCompilationLock = new object (); static readonly object virtualPathsToIgnoreLock = new object (); static readonly char[] virtualPathsToIgnoreSplitChars = {','}; static EventHandlerList events = new EventHandlerList (); static object buildManagerRemoveEntryEvent = new object (); static bool hosted; static Dictionary virtualPathsToIgnore; static bool virtualPathsToIgnoreChecked; static bool haveVirtualPathsToIgnore; static List AppCode_Assemblies = new List(); static List TopLevel_Assemblies = new List(); static Dictionary codeDomProviders; static Dictionary buildCache; static List referencedAssemblies; static List configReferencedAssemblies; static bool getReferencedAssembliesInvoked; static int buildCount; static bool is_precompiled; static bool allowReferencedAssembliesCaching; static List dynamicallyRegisteredAssemblies; static bool? batchCompilationEnabled; static FrameworkName targetFramework; static bool preStartMethodsDone; static bool preStartMethodsRunning; //static bool updatable; unused static Dictionary precompiled; // This is here _only_ for the purpose of unit tests! internal static bool suppressDebugModeMessages; // See comment for the cacheLock field at top of System.Web.Caching/Cache.cs static ReaderWriterLockSlim buildCacheLock; static ulong recursionDepth; internal static bool AllowReferencedAssembliesCaching { get { return allowReferencedAssembliesCaching; } set { allowReferencedAssembliesCaching = value; } } internal static bool IsPrecompiled { get { return is_precompiled; } } internal static event BuildManagerRemoveEntryEventHandler RemoveEntry { add { events.AddHandler (buildManagerRemoveEntryEvent, value); } remove { events.RemoveHandler (buildManagerRemoveEntryEvent, value); } } internal static bool CompilingTopLevelAssemblies { get; set; } internal static bool PreStartMethodsRunning { get { return preStartMethodsRunning; } } public static bool? BatchCompilationEnabled { get { return batchCompilationEnabled; } set { if (preStartMethodsDone) throw new InvalidOperationException ("This method cannot be called after the application's pre-start initialization stage."); batchCompilationEnabled = value; } } public static FrameworkName TargetFramework { get { if (targetFramework == null) { CompilationSection cs = CompilationConfig; string framework; if (cs == null) framework = null; else framework = cs.TargetFramework; if (String.IsNullOrEmpty (framework)) targetFramework = new FrameworkName (".NETFramework,Version=v4.0"); else targetFramework = new FrameworkName (framework); } return targetFramework; } } internal static bool BatchMode { get { if (batchCompilationEnabled != null) return (bool)batchCompilationEnabled; if (!hosted) return false; // Fix for bug #380985 CompilationSection cs = CompilationConfig; if (cs == null) return true; return cs.Batch; } } // Assemblies built from the App_Code directory public static IList CodeAssemblies { get { return AppCode_Assemblies; } } internal static CompilationSection CompilationConfig { get { return WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection; } } internal static bool HaveResources { get; set; } internal static IList TopLevelAssemblies { get { return TopLevel_Assemblies; } } static BuildManager () { hosted = (AppDomain.CurrentDomain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes"; buildCache = new Dictionary (RuntimeHelpers.StringEqualityComparer); buildCacheLock = new ReaderWriterLockSlim (); referencedAssemblies = new List (); recursionDepth = 0; string appPath = HttpRuntime.AppDomainAppPath; string precomp_name = null; is_precompiled = String.IsNullOrEmpty (appPath) ? false : File.Exists ((precomp_name = Path.Combine (appPath, "PrecompiledApp.config"))); if (is_precompiled) is_precompiled = LoadPrecompilationInfo (precomp_name); } internal static void AssertPreStartMethodsRunning () { if (!BuildManager.PreStartMethodsRunning) throw new InvalidOperationException ("This method must be called during the application's pre-start initialization stage."); } // Deal with precompiled sites deployed in a different virtual path static void FixVirtualPaths () { if (precompiled == null) return; string [] parts; int skip = -1; string appVirtualRoot = VirtualPathUtility.AppendTrailingSlash (HttpRuntime.AppDomainAppVirtualPath); foreach (string vpath in precompiled.Keys) { parts = vpath.Split ('/'); for (int i = 0; i < parts.Length; i++) { if (String.IsNullOrEmpty (parts [i])) continue; // The path must be rooted, otherwise PhysicalPath returned // below will be relative to the current request path and // File.Exists will return a false negative. See bug #546053 string test_path = appVirtualRoot + String.Join ("/", parts, i, parts.Length - i); VirtualPath result = GetAbsoluteVirtualPath (test_path); if (result != null && File.Exists (result.PhysicalPath)) { skip = i - 1; break; } } } string app_vpath = HttpRuntime.AppDomainAppVirtualPath; if (skip == -1 || (skip == 0 && app_vpath == "/")) return; if (!app_vpath.EndsWith ("/")) app_vpath = app_vpath + "/"; Dictionary copy = new Dictionary (precompiled); precompiled.Clear (); foreach (KeyValuePair entry in copy) { parts = entry.Key.Split ('/'); string new_path; if (String.IsNullOrEmpty (parts [0])) new_path = app_vpath + String.Join ("/", parts, skip + 1, parts.Length - skip - 1); else new_path = app_vpath + String.Join ("/", parts, skip, parts.Length - skip); entry.Value.VirtualPath = new_path; precompiled.Add (new_path, entry.Value); } } static bool LoadPrecompilationInfo (string precomp_config) { using (XmlTextReader reader = new XmlTextReader (precomp_config)) { reader.MoveToContent (); if (reader.Name != "precompiledApp") return false; /* unused if (reader.HasAttributes) while (reader.MoveToNextAttribute ()) if (reader.Name == "updatable") { updatable = (reader.Value == "true"); break; } */ } string [] compiled = Directory.GetFiles (HttpRuntime.BinDirectory, "*.compiled"); foreach (string str in compiled) LoadCompiled (str); FixVirtualPaths (); return true; } static void LoadCompiled (string filename) { using (XmlTextReader reader = new XmlTextReader (filename)) { reader.MoveToContent (); if (reader.Name == "preserve" && reader.HasAttributes) { reader.MoveToNextAttribute (); string val = reader.Value; // 1 -> app_code subfolder - add the assembly to CodeAssemblies // 2 -> ashx // 3 -> ascx, aspx // 6 -> app_code - add the assembly to CodeAssemblies // 8 -> global.asax // 9 -> App_GlobalResources - set the assembly for HttpContext if (reader.Name == "resultType" && (val == "2" || val == "3" || val == "8")) LoadPageData (reader, true); else if (val == "1" || val == "6") { PreCompilationData pd = LoadPageData (reader, false); CodeAssemblies.Add (Assembly.Load (pd.AssemblyFileName)); } else if (val == "9") { PreCompilationData pd = LoadPageData (reader, false); HttpContext.AppGlobalResourcesAssembly = Assembly.Load (pd.AssemblyFileName); } } } } class PreCompilationData { public string VirtualPath; public string AssemblyFileName; public string TypeName; public Type Type; } static PreCompilationData LoadPageData (XmlTextReader reader, bool store) { PreCompilationData pc_data = new PreCompilationData (); while (reader.MoveToNextAttribute ()) { string name = reader.Name; if (name == "virtualPath") pc_data.VirtualPath = VirtualPathUtility.RemoveTrailingSlash (reader.Value); else if (name == "assembly") pc_data.AssemblyFileName = reader.Value; else if (name == "type") pc_data.TypeName = reader.Value; } if (store) { if (precompiled == null) precompiled = new Dictionary (RuntimeHelpers.StringEqualityComparerCulture); precompiled.Add (pc_data.VirtualPath, pc_data); } return pc_data; } static void AddAssembly (Assembly asm, List al) { if (al.Contains (asm)) return; al.Add (asm); } static void AddPathToIgnore (string vp) { if (virtualPathsToIgnore == null) virtualPathsToIgnore = new Dictionary (RuntimeHelpers.StringEqualityComparerCulture); VirtualPath path = GetAbsoluteVirtualPath (vp); string vpAbsolute = path.Absolute; if (!virtualPathsToIgnore.ContainsKey (vpAbsolute)) { virtualPathsToIgnore.Add (vpAbsolute, true); haveVirtualPathsToIgnore = true; } string vpRelative = path.AppRelative; if (!virtualPathsToIgnore.ContainsKey (vpRelative)) { virtualPathsToIgnore.Add (vpRelative, true); haveVirtualPathsToIgnore = true; } if (!virtualPathsToIgnore.ContainsKey (vp)) { virtualPathsToIgnore.Add (vp, true); haveVirtualPathsToIgnore = true; } } internal static void AddToReferencedAssemblies (Assembly asm) { // should not be used } static void AssertVirtualPathExists (VirtualPath virtualPath) { string realpath; bool dothrow = false; if (virtualPath.IsFake) { realpath = virtualPath.PhysicalPath; if (!File.Exists (realpath) && !Directory.Exists (realpath)) dothrow = true; } else { VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider; string vpAbsolute = virtualPath.Absolute; if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute)) dothrow = true; } if (dothrow) throw new HttpException (404, "The file '" + virtualPath + "' does not exist.", virtualPath.Absolute); } static void Build (VirtualPath vp) { AssertVirtualPathExists (vp); CompilationSection cs = CompilationConfig; lock (bigCompilationLock) { bool entryExists; if (HasCachedItemNoLock (vp.Absolute, out entryExists)) return; if (recursionDepth == 0) referencedAssemblies.Clear (); recursionDepth++; try { BuildInner (vp, cs != null ? cs.Debug : false); if (entryExists && recursionDepth <= 1) // We count only update builds - first time a file // (or a batch) is built doesn't count. buildCount++; } finally { // See http://support.microsoft.com/kb/319947 if (buildCount > cs.NumRecompilesBeforeAppRestart) HttpRuntime.UnloadAppDomain (); recursionDepth--; } } } // This method assumes it is being called with the big compilation lock held static void BuildInner (VirtualPath vp, bool debug) { var builder = new BuildManagerDirectoryBuilder (vp); bool recursive = recursionDepth > 1; List builderGroups = builder.Build (IsSingleBuild (vp, recursive)); if (builderGroups == null) return; string vpabsolute = vp.Absolute; int buildHash = (vpabsolute.GetHashCode () | (int)DateTime.Now.Ticks) + (int)recursionDepth; string assemblyBaseName; AssemblyBuilder abuilder; CompilerType ct; int attempts; bool singleBuild, needMainVpBuild; CompilationException compilationError; // Each group becomes a separate assembly. foreach (BuildProviderGroup group in builderGroups) { needMainVpBuild = false; compilationError = null; assemblyBaseName = null; if (group.Count == 1) { if (recursive || !group.Master) assemblyBaseName = String.Format ("{0}_{1}.{2:x}.", group.NamePrefix, VirtualPathUtility.GetFileName (group [0].VirtualPath), buildHash); singleBuild = true; } else singleBuild = false; if (assemblyBaseName == null) assemblyBaseName = group.NamePrefix + "_"; ct = group.CompilerType; attempts = 3; while (attempts > 0) { abuilder = new AssemblyBuilder (vp, CreateDomProvider (ct), assemblyBaseName); abuilder.CompilerOptions = ct.CompilerParameters; abuilder.AddAssemblyReference (GetReferencedAssemblies () as List ); try { GenerateAssembly (abuilder, group, vp, debug); attempts = 0; } catch (CompilationException ex) { attempts--; if (singleBuild) throw new HttpException ("Single file build failed.", ex); if (attempts == 0) { needMainVpBuild = true; compilationError = ex; break; } CompilerResults results = ex.Results; if (results == null) throw new HttpException ("No results returned from failed compilation.", ex); else RemoveFailedAssemblies (vpabsolute, ex, abuilder, group, results, debug); } } if (needMainVpBuild) { // One last attempt - try to build just the requested path // if it's not built yet or just return without throwing the // exception if it has already been built. if (HasCachedItemNoLock (vpabsolute)) { if (debug) DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:", compilationError, vpabsolute); return; }; // This will trigger a recursive build of the requested vp, // which means only the vp alone will be built (or not); Build (vp); if (HasCachedItemNoLock (vpabsolute)) { if (debug) DescribeCompilationError ("Path '{0}' built successfully, but a compilation exception has been thrown for other files:", compilationError, vpabsolute); return; } // In theory this code is unreachable. If the recursive // build of the main vp failed, then it should have thrown // the build exception. throw new HttpException ("Requested virtual path build failed.", compilationError); } } } static CodeDomProvider CreateDomProvider (CompilerType ct) { if (codeDomProviders == null) codeDomProviders = new Dictionary (); Type type = ct.CodeDomProviderType; if (type == null) { CompilationSection cs = CompilationConfig; CompilerType tmp = GetDefaultCompilerTypeForLanguage (cs.DefaultLanguage, cs); if (tmp != null) type = tmp.CodeDomProviderType; } if (type == null) return null; CodeDomProvider ret; if (codeDomProviders.TryGetValue (type, out ret)) return ret; ret = Activator.CreateInstance (type) as CodeDomProvider; if (ret == null) return null; codeDomProviders.Add (type, ret); return ret; } internal static void CallPreStartMethods () { if (preStartMethodsDone) return; preStartMethodsRunning = true; MethodInfo mi = null; try { List methods = LoadPreStartMethodsFromAssemblies (GetReferencedAssemblies () as List ); if (methods == null || methods.Count == 0) return; foreach (MethodInfo m in methods) { mi = m; m.Invoke (null, null); } } catch (Exception ex) { throw new HttpException ( String.Format ("The pre-application start initialization method {0} on type {1} threw an exception with the following error message: {2}", mi != null ? mi.Name : "UNKNOWN", mi != null ? mi.DeclaringType.FullName : "UNKNOWN", ex.Message), ex ); } finally { preStartMethodsRunning = false; preStartMethodsDone = true; } } static List LoadPreStartMethodsFromAssemblies (List assemblies) { if (assemblies == null || assemblies.Count == 0) return null; var ret = new List (); object[] attributes; Type type; PreApplicationStartMethodAttribute attr; foreach (Assembly asm in assemblies) { try { attributes = asm.GetCustomAttributes (typeof (PreApplicationStartMethodAttribute), false); if (attributes == null || attributes.Length == 0) continue; attr = attributes [0] as PreApplicationStartMethodAttribute; type = attr.Type; if (type == null) continue; } catch { continue; } MethodInfo mi; Exception error = null; try { if (type.IsPublic) mi = type.GetMethod (attr.MethodName, BindingFlags.Static | BindingFlags.Public, null, new Type[] {}, null); else mi = null; } catch (Exception ex) { error = ex; mi = null; } if (mi == null) throw new HttpException ( String.Format ( "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).", asm.FullName, type.FullName, attr.MethodName), error ); ret.Add (mi); } return ret; } public static Type GetGlobalAsaxType () { Type ret = HttpApplicationFactory.AppType; if (ret == null) return typeof (HttpApplication); return ret; } public static Stream CreateCachedFile (string fileName) { if (fileName != null && (fileName == String.Empty || fileName.IndexOf (Path.DirectorySeparatorChar) != -1)) throw new ArgumentException ("Value does not fall within the expected range."); string path = Path.Combine (HttpRuntime.CodegenDir, fileName); return new FileStream (path, FileMode.Create, FileAccess.ReadWrite, FileShare.None); } public static Stream ReadCachedFile (string fileName) { if (fileName != null && (fileName == String.Empty || fileName.IndexOf (Path.DirectorySeparatorChar) != -1)) throw new ArgumentException ("Value does not fall within the expected range."); string path = Path.Combine (HttpRuntime.CodegenDir, fileName); if (!File.Exists (path)) return null; return new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.None); } [MonoDocumentationNote ("Fully implemented but no info on application pre-init stage is available yet.")] public static void AddReferencedAssembly (Assembly assembly) { if (assembly == null) throw new ArgumentNullException ("assembly"); if (preStartMethodsDone) throw new InvalidOperationException ("This method cannot be called after the application's pre-start initialization stage."); if (dynamicallyRegisteredAssemblies == null) dynamicallyRegisteredAssemblies = new List (); if (!dynamicallyRegisteredAssemblies.Contains (assembly)) dynamicallyRegisteredAssemblies.Add (assembly); } [MonoDocumentationNote ("Not used by Mono internally. Needed for MVC3")] public static IWebObjectFactory GetObjectFactory (string virtualPath, bool throwIfNotFound) { if (CompilingTopLevelAssemblies) throw new HttpException ("Method must not be called while compiling the top level assemblies."); Type type; if (is_precompiled) { type = GetPrecompiledType (virtualPath); if (type == null) { if (throwIfNotFound) throw new HttpException (String.Format ("Virtual path '{0}' not found in precompiled application type cache.", virtualPath)); else return null; } return new SimpleWebObjectFactory (type); } Exception compileException = null; try { type = GetCompiledType (virtualPath); } catch (Exception ex) { compileException = ex; type = null; } if (type == null) { if (throwIfNotFound) throw new HttpException (String.Format ("Virtual path '{0}' does not exist.", virtualPath), compileException); return null; } return new SimpleWebObjectFactory (type); } public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType) { return CreateInstanceFromVirtualPath (GetAbsoluteVirtualPath (virtualPath), requiredBaseType); } internal static object CreateInstanceFromVirtualPath (VirtualPath virtualPath, Type requiredBaseType) { if (requiredBaseType == null) throw new NullReferenceException (); // This is what MS does, but // from somewhere else. Type type = GetCompiledType (virtualPath); if (type == null) return null; if (!requiredBaseType.IsAssignableFrom (type)) throw new HttpException (500, String.Format ("Type '{0}' does not inherit from '{1}'.", type.FullName, requiredBaseType.FullName)); return Activator.CreateInstance (type, null); } static void DescribeCompilationError (string format, CompilationException ex, params object[] parms) { StringBuilder sb = new StringBuilder (); string newline = Environment.NewLine; if (parms != null) sb.AppendFormat (format + newline, parms); else sb.Append (format + newline); CompilerResults results = ex != null ? ex.Results : null; if (results == null) sb.Append ("No compiler error information present." + newline); else { sb.Append ("Compiler errors:" + newline); foreach (CompilerError error in results.Errors) sb.Append (" " + error.ToString () + newline); } if (ex != null) { sb.Append (newline + "Exception thrown:" + newline); sb.Append (ex.ToString ()); } ShowDebugModeMessage (sb.ToString ()); } static BuildProvider FindBuildProviderForPhysicalPath (string path, BuildProviderGroup group, HttpRequest req) { if (req == null || String.IsNullOrEmpty (path)) return null; foreach (BuildProvider bp in group) { if (String.Compare (path, req.MapPath (bp.VirtualPath), RuntimeHelpers.StringComparison) == 0) return bp; } return null; } static void GenerateAssembly (AssemblyBuilder abuilder, BuildProviderGroup group, VirtualPath vp, bool debug) { IDictionary deps; BuildManagerCacheItem bmci; string bvp, vpabsolute = vp.Absolute; StringBuilder sb; string newline; int failedCount = 0; if (debug) { newline = Environment.NewLine; sb = new StringBuilder ("Code generation for certain virtual paths in a batch failed. Those files have been removed from the batch." + newline); sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline); } else { newline = null; sb = null; } List failedBuildProviders = null; StringComparison stringComparison = RuntimeHelpers.StringComparison; foreach (BuildProvider bp in group) { bvp = bp.VirtualPath; if (HasCachedItemNoLock (bvp)) continue; try { bp.GenerateCode (abuilder); } catch (Exception ex) { if (String.Compare (bvp, vpabsolute, stringComparison) == 0) { if (ex is CompilationException || ex is ParseException) throw; throw new HttpException ("Code generation failed.", ex); } if (failedBuildProviders == null) failedBuildProviders = new List (); failedBuildProviders.Add (bp); failedCount++; if (sb != null) { if (failedCount > 1) sb.Append (newline); sb.AppendFormat ("Failed file virtual path: {0}; Exception: {1}{2}{1}", bp.VirtualPath, newline, ex); } continue; } deps = bp.ExtractDependencies (); if (deps != null) { foreach (var dep in deps) { bmci = GetCachedItemNoLock (dep.Key); if (bmci == null || bmci.BuiltAssembly == null) continue; abuilder.AddAssemblyReference (bmci.BuiltAssembly); } } } if (sb != null && failedCount > 0) ShowDebugModeMessage (sb.ToString ()); if (failedBuildProviders != null) { foreach (BuildProvider bp in failedBuildProviders) group.Remove (bp); } foreach (Assembly asm in referencedAssemblies) { if (asm == null) continue; abuilder.AddAssemblyReference (asm); } CompilerResults results = abuilder.BuildAssembly (vp); // No results is not an error - it is possible that the assembly builder contained only .asmx and // .ashx files which had no body, just the directive. In such case, no code unit or code file is added // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need // to be added to the cache. Assembly compiledAssembly = results != null ? results.CompiledAssembly : null; try { buildCacheLock.EnterWriteLock (); if (compiledAssembly != null) referencedAssemblies.Add (compiledAssembly); foreach (BuildProvider bp in group) { if (HasCachedItemNoLock (bp.VirtualPath)) continue; StoreInCache (bp, compiledAssembly, results); } } finally { buildCacheLock.ExitWriteLock (); } } static VirtualPath GetAbsoluteVirtualPath (string virtualPath) { string vp; if (!VirtualPathUtility.IsRooted (virtualPath)) { HttpContext ctx = HttpContext.Current; HttpRequest req = ctx != null ? ctx.Request : null; if (req != null) { string fileDir = req.FilePath; if (!String.IsNullOrEmpty (fileDir) && String.Compare (fileDir, "/", StringComparison.Ordinal) != 0) fileDir = VirtualPathUtility.GetDirectory (fileDir); else fileDir = "/"; vp = VirtualPathUtility.Combine (fileDir, virtualPath); } else throw new HttpException ("No context, cannot map paths."); } else vp = virtualPath; return new VirtualPath (vp); } [MonoTODO ("Not implemented, always returns null")] public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath) { return null; // null is ok here until we store the dependency set in the Cache. } [MonoTODO ("Not implemented, always returns null")] public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath, bool ensureIsUpToDate) { return null; // null is ok here until we store the dependency set in the Cache. } static BuildManagerCacheItem GetCachedItem (string vp) { try { buildCacheLock.EnterReadLock (); return GetCachedItemNoLock (vp); } finally { buildCacheLock.ExitReadLock (); } } static BuildManagerCacheItem GetCachedItemNoLock (string vp) { BuildManagerCacheItem ret; if (buildCache.TryGetValue (vp, out ret)) return ret; return null; } internal static Type GetCodeDomProviderType (BuildProvider provider) { CompilerType codeCompilerType; Type codeDomProviderType = null; codeCompilerType = provider.CodeCompilerType; if (codeCompilerType != null) codeDomProviderType = codeCompilerType.CodeDomProviderType; if (codeDomProviderType == null) throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type.")); return codeDomProviderType; } static Type GetPrecompiledType (string virtualPath) { if (precompiled == null || precompiled.Count == 0) return null; PreCompilationData pc_data; var vp = new VirtualPath (virtualPath); if (!precompiled.TryGetValue (vp.Absolute, out pc_data)) if (!precompiled.TryGetValue (virtualPath, out pc_data)) return null; if (pc_data.Type == null) pc_data.Type = Type.GetType (pc_data.TypeName + ", " + pc_data.AssemblyFileName, true); return pc_data.Type; } internal static Type GetPrecompiledApplicationType () { if (!is_precompiled) return null; string appVp = VirtualPathUtility.AppendTrailingSlash (HttpRuntime.AppDomainAppVirtualPath); Type apptype = GetPrecompiledType (VirtualPathUtility.Combine (appVp, "global.asax")); if (apptype == null) apptype = GetPrecompiledType (VirtualPathUtility.Combine (appVp, "Global.asax")); return apptype; } public static Assembly GetCompiledAssembly (string virtualPath) { return GetCompiledAssembly (GetAbsoluteVirtualPath (virtualPath)); } internal static Assembly GetCompiledAssembly (VirtualPath virtualPath) { string vpabsolute = virtualPath.Absolute; if (is_precompiled) { Type type = GetPrecompiledType (vpabsolute); if (type != null) return type.Assembly; } BuildManagerCacheItem bmci = GetCachedItem (vpabsolute); if (bmci != null) return bmci.BuiltAssembly; Build (virtualPath); bmci = GetCachedItem (vpabsolute); if (bmci != null) return bmci.BuiltAssembly; return null; } public static Type GetCompiledType (string virtualPath) { return GetCompiledType (GetAbsoluteVirtualPath (virtualPath)); } internal static Type GetCompiledType (VirtualPath virtualPath) { string vpabsolute = virtualPath.Absolute; if (is_precompiled) { Type type = GetPrecompiledType (vpabsolute); if (type != null) return type; } BuildManagerCacheItem bmci = GetCachedItem (vpabsolute); if (bmci != null) { ReferenceAssemblyInCompilation (bmci); return bmci.Type; } Build (virtualPath); bmci = GetCachedItem (vpabsolute); if (bmci != null) { ReferenceAssemblyInCompilation (bmci); return bmci.Type; } return null; } public static string GetCompiledCustomString (string virtualPath) { return GetCompiledCustomString (GetAbsoluteVirtualPath (virtualPath)); } internal static string GetCompiledCustomString (VirtualPath virtualPath) { string vpabsolute = virtualPath.Absolute; BuildManagerCacheItem bmci = GetCachedItem (vpabsolute); if (bmci != null) return bmci.CompiledCustomString; Build (virtualPath); bmci = GetCachedItem (vpabsolute); if (bmci != null) return bmci.CompiledCustomString; return null; } internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection) { return GetDefaultCompilerTypeForLanguage (language, configSection, true); } internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection, bool throwOnMissing) { // MS throws when accesing a Hashtable, we do here. if (language == null || language.Length == 0) throw new ArgumentNullException ("language"); CompilationSection config; if (configSection == null) config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection; else config = configSection; Compiler compiler = config.Compilers.Get (language); CompilerParameters p; Type type; if (compiler != null) { type = HttpApplication.LoadType (compiler.Type, true); p = new CompilerParameters (); p.CompilerOptions = compiler.CompilerOptions; p.WarningLevel = compiler.WarningLevel; SetCommonParameters (config, p, type, language); return new CompilerType (type, p); } if (CodeDomProvider.IsDefinedLanguage (language)) { CompilerInfo info = CodeDomProvider.GetCompilerInfo (language); p = info.CreateDefaultCompilerParameters (); type = info.CodeDomProviderType; SetCommonParameters (config, p, type, language); return new CompilerType (type, p); } if (throwOnMissing) throw new HttpException (String.Concat ("No compiler for language '", language, "'.")); return null; } public static ICollection GetReferencedAssemblies () { if (getReferencedAssembliesInvoked) return configReferencedAssemblies; if (allowReferencedAssembliesCaching) getReferencedAssembliesInvoked = true; if (configReferencedAssemblies == null) configReferencedAssemblies = new List (); else if (getReferencedAssembliesInvoked) configReferencedAssemblies.Clear (); CompilationSection compConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection; if (compConfig == null) return configReferencedAssemblies; bool addAssembliesInBin = false; foreach (AssemblyInfo info in compConfig.Assemblies) { if (info.Assembly == "*") addAssembliesInBin = is_precompiled ? false : true; else LoadAssembly (info, configReferencedAssemblies); } foreach (Assembly topLevelAssembly in TopLevelAssemblies) configReferencedAssemblies.Add (topLevelAssembly); foreach (string assLocation in WebConfigurationManager.ExtraAssemblies) LoadAssembly (assLocation, configReferencedAssemblies); if (dynamicallyRegisteredAssemblies != null) foreach (Assembly registeredAssembly in dynamicallyRegisteredAssemblies) configReferencedAssemblies.Add (registeredAssembly); // Precompiled sites unconditionally load all assemblies from bin/ (fix for // bug #502016) if (is_precompiled || addAssembliesInBin) { foreach (string s in HttpApplication.BinDirectoryAssemblies) { try { LoadAssembly (s, configReferencedAssemblies); } catch (BadImageFormatException) { // ignore silently } } } return configReferencedAssemblies; } // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers public static Type GetType (string typeName, bool throwOnError) { return GetType (typeName, throwOnError, false); } public static Type GetType (string typeName, bool throwOnError, bool ignoreCase) { if (String.IsNullOrEmpty (typeName)) throw new HttpException ("Type name must not be empty."); Type ret = null; Exception ex = null; try { string wantedAsmName; string wantedTypeName; int comma = typeName.IndexOf (','); if (comma > 0 && comma < typeName.Length - 1) { var aname = new AssemblyName (typeName.Substring (comma + 1)); wantedAsmName = aname.ToString (); wantedTypeName = typeName.Substring (0, comma); } else { wantedAsmName = null; wantedTypeName = typeName; } var assemblies = new List (); assemblies.AddRange (BuildManager.GetReferencedAssemblies () as List ); assemblies.AddRange (TopLevel_Assemblies); Type appType = HttpApplicationFactory.AppType; if (appType != null) assemblies.Add (appType.Assembly); foreach (Assembly asm in assemblies) { if (asm == null) continue; if (wantedAsmName != null) { // So dumb... if (String.Compare (wantedAsmName, asm.GetName ().ToString (), StringComparison.Ordinal) == 0) { ret = asm.GetType (wantedTypeName, throwOnError, ignoreCase); if (ret != null) return ret; } continue; } ret = asm.GetType (wantedTypeName, false, ignoreCase); if (ret != null) return ret; } } catch (Exception e) { ex = e; } if (throwOnError) throw new HttpException ("Failed to find the specified type.", ex); return null; } public static ICollection GetVirtualPathDependencies (string virtualPath) { return GetVirtualPathDependencies (virtualPath, null); } internal static ICollection GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider) { BuildProvider provider = bprovider; if (provider == null) { CompilationSection cs = CompilationConfig; if (cs == null) return null; provider = BuildManagerDirectoryBuilder.GetBuildProvider (virtualPath, cs.BuildProviders); } if (provider == null) return null; IDictionary deps = provider.ExtractDependencies (); if (deps == null) return null; return (ICollection)deps.Keys; } internal static bool HasCachedItemNoLock (string vp, out bool entryExists) { BuildManagerCacheItem item; if (buildCache.TryGetValue (vp, out item)) { entryExists = true; return item != null; } entryExists = false; return false; } internal static bool HasCachedItemNoLock (string vp) { bool dummy; return HasCachedItemNoLock (vp, out dummy); } internal static bool IgnoreVirtualPath (string virtualPath) { if (!virtualPathsToIgnoreChecked) { lock (virtualPathsToIgnoreLock) { if (!virtualPathsToIgnoreChecked) LoadVirtualPathsToIgnore (); virtualPathsToIgnoreChecked = true; } } if (!haveVirtualPathsToIgnore) return false; if (virtualPathsToIgnore.ContainsKey (virtualPath)) return true; return false; } static bool IsSingleBuild (VirtualPath vp, bool recursive) { if (String.Compare (vp.AppRelative, "~/global.asax", StringComparison.OrdinalIgnoreCase) == 0) return true; if (!BatchMode) return true; return recursive; } static void LoadAssembly (string path, List al) { AddAssembly (Assembly.LoadFrom (path), al); } static void LoadAssembly (AssemblyInfo info, List al) { AddAssembly (Assembly.Load (info.Assembly), al); } static void LoadVirtualPathsToIgnore () { NameValueCollection appSettings = WebConfigurationManager.AppSettings; if (appSettings == null) return; string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"]; string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"]; if (!String.IsNullOrEmpty (pathsFromConfig)) { string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars); string path; foreach (string p in paths) { path = p.Trim (); if (path.Length == 0) continue; AddPathToIgnore (path); } } if (!String.IsNullOrEmpty (pathsFromFile)) { string realpath; HttpContext ctx = HttpContext.Current; HttpRequest req = ctx != null ? ctx.Request : null; if (req == null) throw new HttpException ("Missing context, cannot continue."); realpath = req.MapPath (pathsFromFile); if (!File.Exists (realpath)) return; string[] paths = File.ReadAllLines (realpath); if (paths == null || paths.Length == 0) return; string path; foreach (string p in paths) { path = p.Trim (); if (path.Length == 0) continue; AddPathToIgnore (path); } } } static void OnEntryRemoved (string vp) { BuildManagerRemoveEntryEventHandler eh = events [buildManagerRemoveEntryEvent] as BuildManagerRemoveEntryEventHandler; if (eh != null) eh (new BuildManagerRemoveEntryEventArgs (vp, HttpContext.Current)); } static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason) { string virtualPath; if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX)) virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH); else return; try { buildCacheLock.EnterWriteLock (); if (HasCachedItemNoLock (virtualPath)) { buildCache [virtualPath] = null; OnEntryRemoved (virtualPath); } } finally { buildCacheLock.ExitWriteLock (); } } static void ReferenceAssemblyInCompilation (BuildManagerCacheItem bmci) { if (recursionDepth == 0 || referencedAssemblies.Contains (bmci.BuiltAssembly)) return; referencedAssemblies.Add (bmci.BuiltAssembly); } static void RemoveFailedAssemblies (string requestedVirtualPath, CompilationException ex, AssemblyBuilder abuilder, BuildProviderGroup group, CompilerResults results, bool debug) { StringBuilder sb; string newline; if (debug) { newline = Environment.NewLine; sb = new StringBuilder ("Compilation of certain files in a batch failed. Another attempt to compile the batch will be made." + newline); sb.Append ("Since you're running in debug mode, here's some more information about the error:" + newline); } else { newline = null; sb = null; } var failedBuildProviders = new List (); BuildProvider bp; HttpContext ctx = HttpContext.Current; HttpRequest req = ctx != null ? ctx.Request : null; bool rethrow = false; foreach (CompilerError error in results.Errors) { if (error.IsWarning) continue; bp = abuilder.GetBuildProviderForPhysicalFilePath (error.FileName); if (bp == null) { bp = FindBuildProviderForPhysicalPath (error.FileName, group, req); if (bp == null) continue; } if (String.Compare (bp.VirtualPath, requestedVirtualPath, StringComparison.Ordinal) == 0) rethrow = true; if (!failedBuildProviders.Contains (bp)) { failedBuildProviders.Add (bp); if (sb != null) sb.AppendFormat ("\t{0}{1}", bp.VirtualPath, newline); } if (sb != null) sb.AppendFormat ("\t\t{0}{1}", error, newline); } foreach (BuildProvider fbp in failedBuildProviders) group.Remove (fbp); if (sb != null) { sb.AppendFormat ("{0}The following exception has been thrown for the file(s) listed above:{0}{1}", newline, ex.ToString ()); ShowDebugModeMessage (sb.ToString ()); sb = null; } if (rethrow) throw new HttpException ("Compilation failed.", ex); } static void SetCommonParameters (CompilationSection config, CompilerParameters p, Type compilerType, string language) { p.IncludeDebugInformation = config.Debug; MonoSettingsSection mss = WebConfigurationManager.GetSection ("system.web/monoSettings") as MonoSettingsSection; if (mss == null || !mss.UseCompilersCompatibility) return; Compiler compiler = mss.CompilersCompatibility.Get (language); if (compiler == null) return; Type type = HttpApplication.LoadType (compiler.Type, false); if (type != compilerType) return; p.CompilerOptions = String.Concat (p.CompilerOptions, " ", compiler.CompilerOptions); } static void ShowDebugModeMessage (string msg) { if (suppressDebugModeMessages) return; Console.Error.WriteLine (); Console.Error.WriteLine ("******* DEBUG MODE MESSAGE *******"); Console.Error.WriteLine (msg); Console.Error.WriteLine ("******* DEBUG MODE MESSAGE *******"); Console.Error.WriteLine (); } static void StoreInCache (BuildProvider bp, Assembly compiledAssembly, CompilerResults results) { string virtualPath = bp.VirtualPath; var item = new BuildManagerCacheItem (compiledAssembly, bp, results); if (buildCache.ContainsKey (virtualPath)) buildCache [virtualPath] = item; else buildCache.Add (virtualPath, item); HttpContext ctx = HttpContext.Current; HttpRequest req = ctx != null ? ctx.Request : null; CacheDependency dep; if (req != null) { IDictionary deps = bp.ExtractDependencies (); var files = new List (); string physicalPath; physicalPath = req.MapPath (virtualPath); if (File.Exists (physicalPath)) files.Add (physicalPath); if (deps != null && deps.Count > 0) { foreach (var d in deps) { physicalPath = req.MapPath (d.Key); if (!File.Exists (physicalPath)) continue; if (!files.Contains (physicalPath)) files.Add (physicalPath); } } dep = new CacheDependency (files.ToArray ()); } else dep = null; HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath, true, dep, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, new CacheItemRemovedCallback (OnVirtualPathChanged)); } } }