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
);