Merge branch 'cecil-light'
[mono.git] / mcs / class / System.Web / System.Web.Compilation / BuildManagerDirectoryBuilder.cs
1 //
2 // System.Web.Compilation.BuildManagerDirectoryBuilder
3 //
4 // Authors:
5 //      Marek Habersack (mhabersack@novell.com)
6 //
7 // (C) 2008-2009 Novell, Inc (http://www.novell.com)
8 //
9
10 //
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:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
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.
29 //
30
31 using System;
32 using System.Collections.Generic;
33 using System.Reflection;
34 using System.Web;
35 using System.Web.Configuration;
36 using System.Web.Hosting;
37 using System.Web.Util;
38
39 namespace System.Web.Compilation
40 {
41         sealed class BuildManagerDirectoryBuilder
42         {
43                 sealed class BuildProviderItem
44                 {
45                         public BuildProvider Provider;
46                         public int ListIndex;
47                         public int ParentIndex;
48                         
49                         public BuildProviderItem (BuildProvider bp, int listIndex, int parentIndex)
50                         {
51                                 this.Provider = bp;
52                                 this.ListIndex = listIndex;
53                                 this.ParentIndex = parentIndex;
54                         }
55                 }
56                 
57                 readonly VirtualPath virtualPath;
58                 readonly string virtualPathDirectory;
59                 CompilationSection compilationSection;
60                 Dictionary <string, BuildProvider> buildProviders;
61                 VirtualPathProvider vpp;
62                 
63                 CompilationSection CompilationSection {
64                         get {
65                                 if (compilationSection == null)
66                                         compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
67                                 return compilationSection;
68                         }
69                 }
70                 
71                 public BuildManagerDirectoryBuilder (VirtualPath virtualPath)
72                 {
73                         if (virtualPath == null)
74                                 throw new ArgumentNullException ("virtualPath");
75
76                         this.vpp = HostingEnvironment.VirtualPathProvider;
77                         this.virtualPath = virtualPath;
78                         this.virtualPathDirectory = VirtualPathUtility.GetDirectory (virtualPath.Absolute);
79                 }
80
81                 public List <BuildProviderGroup> Build (bool single)
82                 {
83                         if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/")) {
84                                 var themebp = new ThemeDirectoryBuildProvider ();
85                                 themebp.SetVirtualPath (virtualPath);
86                                 
87                                 return GetSingleBuildProviderGroup (themebp);
88                         }
89
90                         CompilationSection section = CompilationSection;
91                         BuildProviderCollection bpcoll = section != null ? section.BuildProviders : null;
92
93                         if (bpcoll == null || bpcoll.Count == 0)
94                                 return null;
95                         
96                         if (virtualPath.IsFake) {
97                                 BuildProvider bp = GetBuildProvider (virtualPath, bpcoll);
98
99                                 if (bp == null)
100                                         return null;
101
102                                 return GetSingleBuildProviderGroup (bp);
103                         }
104
105                         if (single) {
106                                 AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
107                         } else {
108                                 var cache = new Dictionary <string, bool> (RuntimeHelpers.StringEqualityComparer);
109                                 AddVirtualDir (GetVirtualDirectory (virtualPath.Absolute), bpcoll, cache);
110                                 cache = null;
111                                 if (buildProviders == null || buildProviders.Count == 0)
112                                         AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
113                         }
114
115                         if (buildProviders == null || buildProviders.Count == 0)
116                                         return null;
117                         
118                         var buildProviderGroups = new List <BuildProviderGroup> ();
119                         foreach (BuildProvider bp in buildProviders.Values)
120                                 AssignToGroup (bp, buildProviderGroups);
121
122                         if (buildProviderGroups == null || buildProviderGroups.Count == 0) {
123                                 buildProviderGroups = null;
124                                 return null;
125                         }
126                         
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 ();                 
130                         
131                         return buildProviderGroups;
132                 }
133                 
134                 bool AddBuildProvider (BuildProvider buildProvider)
135                 {
136                         if (buildProviders == null)
137                                 buildProviders = new Dictionary <string, BuildProvider> (RuntimeHelpers.StringEqualityComparer);
138                         
139                         string bpPath = buildProvider.VirtualPath;
140                         if (buildProviders.ContainsKey (bpPath))
141                                 return false;
142
143                         buildProviders.Add (bpPath, buildProvider);
144                         return true;
145                 }
146                 
147                 void AddVirtualDir (VirtualDirectory vdir, BuildProviderCollection bpcoll, Dictionary <string, bool> cache)
148                 {
149                         if (vdir == null)
150                                 return;
151                         
152                         BuildProvider bp;
153                         IDictionary <string, bool> deps;
154                         var dirs = new List <string> ();
155                         string fileVirtualPath;
156                         
157                         foreach (VirtualFile file in vdir.Files) {
158                                 fileVirtualPath = file.VirtualPath;
159                                 if (BuildManager.IgnoreVirtualPath (fileVirtualPath))
160                                         continue;
161                                 
162                                 bp = GetBuildProvider (fileVirtualPath, bpcoll);
163                                 if (bp == null)
164                                         continue;
165                                 if (!AddBuildProvider (bp))
166                                         continue;
167                                 
168                                 deps = bp.ExtractDependencies ();
169                                 if (deps == null)
170                                         continue;
171
172                                 string depDir, s;
173                                 dirs.Clear ();
174                                 foreach (var dep in deps) {
175                                         s = dep.Key;
176                                         depDir = VirtualPathUtility.GetDirectory (s); // dependencies are assumed to contain absolute paths
177                                         if (cache.ContainsKey (depDir))
178                                                 continue;
179                                         cache.Add (depDir, true);
180                                         AddVirtualDir (GetVirtualDirectory (s), bpcoll, cache);
181                                 }
182                         }
183                 }
184                 
185                 void AddVirtualFile (VirtualFile file, BuildProviderCollection bpcoll)
186                 {
187                         if (file == null || BuildManager.IgnoreVirtualPath (file.VirtualPath))
188                                 return;
189                         
190                         BuildProvider bp = GetBuildProvider (file.VirtualPath, bpcoll);
191                         if (bp == null)
192                                 return;
193                         AddBuildProvider (bp);
194                 }
195
196                 List <BuildProviderGroup> GetSingleBuildProviderGroup (BuildProvider bp)
197                 {
198                         var ret = new List <BuildProviderGroup> ();
199                         var group = new BuildProviderGroup ();
200                         group.AddProvider (bp);
201                         ret.Add (group);
202
203                         return ret;
204                 }
205                 
206                 VirtualDirectory GetVirtualDirectory (string virtualPath)
207                 {
208                         if (!vpp.DirectoryExists (VirtualPathUtility.GetDirectory (virtualPath)))
209                                 return null;
210
211                         return vpp.GetDirectory (virtualPath);
212                 }
213
214                 VirtualFile GetVirtualFile (string virtualPath)
215                 {
216                         if (!vpp.FileExists (virtualPath))
217                                 return null;
218                         
219                         return vpp.GetFile (virtualPath);
220                 }
221
222                 Type GetBuildProviderCodeDomType (BuildProvider bp)
223                 {
224                         CompilerType ct = bp.CodeCompilerType;
225                         if (ct == null) {
226                                 string language = bp.LanguageName;
227
228                                 if (String.IsNullOrEmpty (language))
229                                         language = CompilationSection.DefaultLanguage;
230
231                                 ct = BuildManager.GetDefaultCompilerTypeForLanguage (language, CompilationSection, false);
232                         }
233
234                         Type ret = ct != null ? ct.CodeDomProviderType : null;
235                         if (ret == null)
236                                 throw new HttpException ("Unable to determine code compilation language provider for virtual path '" + bp.VirtualPath + "'.");
237
238                         return ret;
239                 }
240                 
241                 void AssignToGroup (BuildProvider buildProvider, List <BuildProviderGroup> groups)
242                 {
243                         if (IsDependencyCycle (buildProvider))
244                                 throw new HttpException ("Dependency cycles are not suppported: " + buildProvider.VirtualPath);
245
246                         BuildProviderGroup myGroup = null;
247                         string bpVirtualPath = buildProvider.VirtualPath;
248                         string bpPath = VirtualPathUtility.GetDirectory (bpVirtualPath);
249                         bool canAdd;
250
251                         if (BuildManager.HasCachedItemNoLock (buildProvider.VirtualPath))
252                                 return;                 
253
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);
260                         } else {
261                                 Type bpCodeDomType = GetBuildProviderCodeDomType (buildProvider);
262                                 foreach (BuildProviderGroup group in groups) {
263                                         if (group.Standalone)
264                                                 continue;
265                                         
266                                         if (group.Count == 0) {
267                                                 myGroup = group;
268                                                 break;
269                                         }
270
271                                         canAdd = true;
272                                         foreach (BuildProvider bp in group) {
273                                                 if (IsDependency (buildProvider, bp)) {
274                                                         canAdd = false;
275                                                         break;
276                                                 }
277                                         
278                                                 // There should be one assembly per virtual dir
279                                                 if (String.Compare (bpPath, VirtualPathUtility.GetDirectory (bp.VirtualPath), stringComparison) != 0) {
280                                                         canAdd = false;
281                                                         break;
282                                                 }
283
284                                                 // Different languages go to different assemblies
285                                                 if (bpCodeDomType != null) {
286                                                         Type type = GetBuildProviderCodeDomType (bp);
287                                                         if (type != null) {
288                                                                 if (type != bpCodeDomType) {
289                                                                         canAdd = false;
290                                                                         break;
291                                                                 }
292                                                         }
293                                                 }
294                                         }
295
296                                         if (!canAdd)
297                                                 continue;
298
299                                         myGroup = group;
300                                         break;
301                                 }
302                                 
303                                 if (myGroup == null) {
304                                         myGroup = new BuildProviderGroup ();
305                                         InsertGroup (myGroup, groups);
306                                 }
307                         }
308                         
309                         myGroup.AddProvider (buildProvider);
310                         if (String.Compare (bpPath, virtualPathDirectory, stringComparison) == 0)
311                                 myGroup.Master = true;
312                 }
313
314                 void InsertGroup (BuildProviderGroup group, List <BuildProviderGroup> groups)
315                 {
316                         if (group.Application) {
317                                 groups.Insert (groups.Count - 1, group);
318                                 return;
319                         }
320
321                         int index;
322                         if (group.Standalone)
323                                 index = groups.FindLastIndex (SkipApplicationGroup);
324                         else
325                                 index = groups.FindLastIndex (SkipStandaloneGroups);
326
327                         if (index == -1)
328                                 groups.Add (group);
329                         else
330                                 groups.Insert (index == 0 ? 0 : index - 1, group);
331                 }
332
333                 static bool SkipStandaloneGroups (BuildProviderGroup group)
334                 {
335                         if (group == null)
336                                 return false;
337                         
338                         return group.Standalone;
339                 }
340
341                 static bool SkipApplicationGroup (BuildProviderGroup group)
342                 {
343                         if (group == null)
344                                 return false;
345
346                         return group.Application;
347                 }
348                 
349                 bool IsDependency (BuildProvider bp1, BuildProvider bp2)
350                 {
351                         IDictionary <string, bool> deps = bp1.ExtractDependencies ();
352                         if (deps == null)
353                                 return false;
354
355                         if (deps.ContainsKey (bp2.VirtualPath))
356                                 return true;
357
358                         BuildProvider bp;
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))
362                                         continue;
363
364                                 if (IsDependency (bp, bp2))
365                                         return true;
366                         }
367                         
368                         return false;
369                 }
370                 
371                 bool IsDependencyCycle (BuildProvider buildProvider)
372                 {
373                         var cache = new Dictionary <BuildProvider, bool> ();
374                         cache.Add (buildProvider, true);
375                         return IsDependencyCycle (cache, buildProvider.ExtractDependencies ());
376                 }
377
378                 bool IsDependencyCycle (Dictionary <BuildProvider, bool> cache, IDictionary <string, bool> deps)
379                 {
380                         if (deps == null)
381                                 return false;
382
383                         BuildProvider bp;
384                         foreach (var d in deps) {
385                                 if (!buildProviders.TryGetValue (d.Key, out bp))
386                                         continue;
387                                 if (cache.ContainsKey (bp))
388                                         return true;
389                                 cache.Add (bp, true);
390                                 if (IsDependencyCycle (cache, bp.ExtractDependencies ()))
391                                         return true;
392                                 cache.Remove (bp);
393                         }
394                         
395                         return false;
396                 }
397
398                 public static BuildProvider GetBuildProvider (string virtualPath, BuildProviderCollection coll)
399                 {
400                         return GetBuildProvider (new VirtualPath (virtualPath), coll);
401                 }
402                 
403                 public static BuildProvider GetBuildProvider (VirtualPath virtualPath, BuildProviderCollection coll)
404                 {
405                         if (virtualPath == null || String.IsNullOrEmpty (virtualPath.Original) || coll == null)
406                                 return null;
407                         
408                         string extension = virtualPath.Extension;
409                         BuildProvider bp = coll.GetProviderInstanceForExtension (extension);
410                         if (bp == null) {
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 ();
415
416                                 if (bp != null)
417                                         bp.SetVirtualPath (virtualPath);
418                                 
419                                 return bp;
420                         }
421                         
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)
426                                         return null;
427                         }
428
429                         bp.SetVirtualPath (virtualPath);
430                         return bp;
431                 }
432         }
433 }
434