2 // System.Web.Compilation.BuildManagerDirectoryBuilder
5 // Marek Habersack (mhabersack@novell.com)
7 // (C) 2008-2009 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections.Generic;
33 using System.Reflection;
35 using System.Web.Configuration;
36 using System.Web.Hosting;
37 using System.Web.Util;
39 namespace System.Web.Compilation
41 sealed class BuildManagerDirectoryBuilder
43 sealed class BuildProviderItem
45 public BuildProvider Provider;
47 public int ParentIndex;
49 public BuildProviderItem (BuildProvider bp, int listIndex, int parentIndex)
52 this.ListIndex = listIndex;
53 this.ParentIndex = parentIndex;
57 readonly VirtualPath virtualPath;
58 readonly string virtualPathDirectory;
59 CompilationSection compilationSection;
60 Dictionary <string, BuildProvider> buildProviders;
61 VirtualPathProvider vpp;
63 CompilationSection CompilationSection {
65 if (compilationSection == null)
66 compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
67 return compilationSection;
71 public BuildManagerDirectoryBuilder (VirtualPath virtualPath)
73 if (virtualPath == null)
74 throw new ArgumentNullException ("virtualPath");
76 this.vpp = HostingEnvironment.VirtualPathProvider;
77 this.virtualPath = virtualPath;
78 this.virtualPathDirectory = VirtualPathUtility.GetDirectory (virtualPath.Absolute);
81 public List <BuildProviderGroup> Build (bool single)
83 if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/")) {
84 var themebp = new ThemeDirectoryBuildProvider ();
85 themebp.SetVirtualPath (virtualPath);
87 return GetSingleBuildProviderGroup (themebp);
90 CompilationSection section = CompilationSection;
91 BuildProviderCollection bpcoll = section != null ? section.BuildProviders : null;
93 if (bpcoll == null || bpcoll.Count == 0)
96 if (virtualPath.IsFake) {
97 BuildProvider bp = GetBuildProvider (virtualPath, bpcoll);
102 return GetSingleBuildProviderGroup (bp);
106 AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
108 var cache = new Dictionary <string, bool> (RuntimeHelpers.StringEqualityComparer);
109 AddVirtualDir (GetVirtualDirectory (virtualPath.Absolute), bpcoll, cache);
111 if (buildProviders == null || buildProviders.Count == 0)
112 AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
115 if (buildProviders == null || buildProviders.Count == 0)
118 var buildProviderGroups = new List <BuildProviderGroup> ();
119 foreach (BuildProvider bp in buildProviders.Values)
120 AssignToGroup (bp, buildProviderGroups);
122 if (buildProviderGroups == null || buildProviderGroups.Count == 0) {
123 buildProviderGroups = null;
127 // We need to reverse the order, so that the build happens from the least
128 // dependant assemblies to the most dependant ones, more or less.
129 buildProviderGroups.Reverse ();
131 return buildProviderGroups;
134 bool AddBuildProvider (BuildProvider buildProvider)
136 if (buildProviders == null)
137 buildProviders = new Dictionary <string, BuildProvider> (RuntimeHelpers.StringEqualityComparer);
139 string bpPath = buildProvider.VirtualPath;
140 if (buildProviders.ContainsKey (bpPath))
143 buildProviders.Add (bpPath, buildProvider);
147 void AddVirtualDir (VirtualDirectory vdir, BuildProviderCollection bpcoll, Dictionary <string, bool> cache)
153 IDictionary <string, bool> deps;
154 var dirs = new List <string> ();
155 string fileVirtualPath;
157 foreach (VirtualFile file in vdir.Files) {
158 fileVirtualPath = file.VirtualPath;
159 if (BuildManager.IgnoreVirtualPath (fileVirtualPath))
162 bp = GetBuildProvider (fileVirtualPath, bpcoll);
165 if (!AddBuildProvider (bp))
168 deps = bp.ExtractDependencies ();
174 foreach (var dep in deps) {
176 depDir = VirtualPathUtility.GetDirectory (s); // dependencies are assumed to contain absolute paths
177 if (cache.ContainsKey (depDir))
179 cache.Add (depDir, true);
180 AddVirtualDir (GetVirtualDirectory (s), bpcoll, cache);
185 void AddVirtualFile (VirtualFile file, BuildProviderCollection bpcoll)
187 if (file == null || BuildManager.IgnoreVirtualPath (file.VirtualPath))
190 BuildProvider bp = GetBuildProvider (file.VirtualPath, bpcoll);
193 AddBuildProvider (bp);
196 List <BuildProviderGroup> GetSingleBuildProviderGroup (BuildProvider bp)
198 var ret = new List <BuildProviderGroup> ();
199 var group = new BuildProviderGroup ();
200 group.AddProvider (bp);
206 VirtualDirectory GetVirtualDirectory (string virtualPath)
208 if (!vpp.DirectoryExists (VirtualPathUtility.GetDirectory (virtualPath)))
211 return vpp.GetDirectory (virtualPath);
214 VirtualFile GetVirtualFile (string virtualPath)
216 if (!vpp.FileExists (virtualPath))
219 return vpp.GetFile (virtualPath);
222 Type GetBuildProviderCodeDomType (BuildProvider bp)
224 CompilerType ct = bp.CodeCompilerType;
226 string language = bp.LanguageName;
228 if (String.IsNullOrEmpty (language))
229 language = CompilationSection.DefaultLanguage;
231 ct = BuildManager.GetDefaultCompilerTypeForLanguage (language, CompilationSection, false);
234 Type ret = ct != null ? ct.CodeDomProviderType : null;
236 throw new HttpException ("Unable to determine code compilation language provider for virtual path '" + bp.VirtualPath + "'.");
241 void AssignToGroup (BuildProvider buildProvider, List <BuildProviderGroup> groups)
243 if (IsDependencyCycle (buildProvider))
244 throw new HttpException ("Dependency cycles are not suppported: " + buildProvider.VirtualPath);
246 BuildProviderGroup myGroup = null;
247 string bpVirtualPath = buildProvider.VirtualPath;
248 string bpPath = VirtualPathUtility.GetDirectory (bpVirtualPath);
251 if (BuildManager.HasCachedItemNoLock (buildProvider.VirtualPath))
254 StringComparison stringComparison = RuntimeHelpers.StringComparison;
255 if (buildProvider is ApplicationFileBuildProvider || buildProvider is ThemeDirectoryBuildProvider) {
256 // global.asax and theme directory go into their own assemblies
257 myGroup = new BuildProviderGroup ();
258 myGroup.Standalone = true;
259 InsertGroup (myGroup, groups);
261 Type bpCodeDomType = GetBuildProviderCodeDomType (buildProvider);
262 foreach (BuildProviderGroup group in groups) {
263 if (group.Standalone)
266 if (group.Count == 0) {
272 foreach (BuildProvider bp in group) {
273 if (IsDependency (buildProvider, bp)) {
278 // There should be one assembly per virtual dir
279 if (String.Compare (bpPath, VirtualPathUtility.GetDirectory (bp.VirtualPath), stringComparison) != 0) {
284 // Different languages go to different assemblies
285 if (bpCodeDomType != null) {
286 Type type = GetBuildProviderCodeDomType (bp);
288 if (type != bpCodeDomType) {
303 if (myGroup == null) {
304 myGroup = new BuildProviderGroup ();
305 InsertGroup (myGroup, groups);
309 myGroup.AddProvider (buildProvider);
310 if (String.Compare (bpPath, virtualPathDirectory, stringComparison) == 0)
311 myGroup.Master = true;
314 void InsertGroup (BuildProviderGroup group, List <BuildProviderGroup> groups)
316 if (group.Application) {
317 groups.Insert (groups.Count - 1, group);
322 if (group.Standalone)
323 index = groups.FindLastIndex (SkipApplicationGroup);
325 index = groups.FindLastIndex (SkipStandaloneGroups);
330 groups.Insert (index == 0 ? 0 : index - 1, group);
333 static bool SkipStandaloneGroups (BuildProviderGroup group)
338 return group.Standalone;
341 static bool SkipApplicationGroup (BuildProviderGroup group)
346 return group.Application;
349 bool IsDependency (BuildProvider bp1, BuildProvider bp2)
351 IDictionary <string, bool> deps = bp1.ExtractDependencies ();
355 if (deps.ContainsKey (bp2.VirtualPath))
359 // It won't loop forever as by the time this method is called, we are sure there are no cycles
360 foreach (var dep in deps) {
361 if (!buildProviders.TryGetValue (dep.Key, out bp))
364 if (IsDependency (bp, bp2))
371 bool IsDependencyCycle (BuildProvider buildProvider)
373 var cache = new Dictionary <BuildProvider, bool> ();
374 cache.Add (buildProvider, true);
375 return IsDependencyCycle (cache, buildProvider.ExtractDependencies ());
378 bool IsDependencyCycle (Dictionary <BuildProvider, bool> cache, IDictionary <string, bool> deps)
384 foreach (var d in deps) {
385 if (!buildProviders.TryGetValue (d.Key, out bp))
387 if (cache.ContainsKey (bp))
389 cache.Add (bp, true);
390 if (IsDependencyCycle (cache, bp.ExtractDependencies ()))
398 public static BuildProvider GetBuildProvider (string virtualPath, BuildProviderCollection coll)
400 return GetBuildProvider (new VirtualPath (virtualPath), coll);
403 public static BuildProvider GetBuildProvider (VirtualPath virtualPath, BuildProviderCollection coll)
405 if (virtualPath == null || String.IsNullOrEmpty (virtualPath.Original) || coll == null)
408 string extension = virtualPath.Extension;
409 BuildProvider bp = coll.GetProviderInstanceForExtension (extension);
411 if (String.Compare (extension, ".asax", StringComparison.OrdinalIgnoreCase) == 0)
412 bp = new ApplicationFileBuildProvider ();
413 else if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/"))
414 bp = new ThemeDirectoryBuildProvider ();
417 bp.SetVirtualPath (virtualPath);
422 object[] attrs = bp.GetType ().GetCustomAttributes (typeof (BuildProviderAppliesToAttribute), true);
423 if (attrs != null && attrs.Length != 0) {
424 BuildProviderAppliesTo appliesTo = ((BuildProviderAppliesToAttribute)attrs [0]).AppliesTo;
425 if ((appliesTo & BuildProviderAppliesTo.Web) == 0)
429 bp.SetVirtualPath (virtualPath);