BuildManagerDirectoryBuilder.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. //
  2. // System.Web.Compilation.BuildManagerDirectoryBuilder
  3. //
  4. // Authors:
  5. // Marek Habersack ([email protected])
  6. //
  7. // (C) 2008-2009 Novell, Inc (http://www.novell.com)
  8. //
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining
  11. // a copy of this software and associated documentation files (the
  12. // "Software"), to deal in the Software without restriction, including
  13. // without limitation the rights to use, copy, modify, merge, publish,
  14. // distribute, sublicense, and/or sell copies of the Software, and to
  15. // permit persons to whom the Software is furnished to do so, subject to
  16. // the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be
  19. // included in all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. //
  29. using System;
  30. using System.Collections.Generic;
  31. using System.Reflection;
  32. using System.Web;
  33. using System.Web.Configuration;
  34. using System.Web.Hosting;
  35. using System.Web.Util;
  36. namespace System.Web.Compilation
  37. {
  38. sealed class BuildManagerDirectoryBuilder
  39. {
  40. sealed class BuildProviderItem
  41. {
  42. public BuildProvider Provider;
  43. public int ListIndex;
  44. public int ParentIndex;
  45. public BuildProviderItem (BuildProvider bp, int listIndex, int parentIndex)
  46. {
  47. this.Provider = bp;
  48. this.ListIndex = listIndex;
  49. this.ParentIndex = parentIndex;
  50. }
  51. }
  52. readonly VirtualPath virtualPath;
  53. readonly string virtualPathDirectory;
  54. CompilationSection compilationSection;
  55. Dictionary <string, BuildProvider> buildProviders;
  56. IEqualityComparer <string> dictionaryComparer;
  57. StringComparison stringComparer;
  58. VirtualPathProvider vpp;
  59. CompilationSection CompilationSection {
  60. get {
  61. if (compilationSection == null)
  62. compilationSection = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
  63. return compilationSection;
  64. }
  65. }
  66. public BuildManagerDirectoryBuilder (VirtualPath virtualPath)
  67. {
  68. if (virtualPath == null)
  69. throw new ArgumentNullException ("virtualPath");
  70. this.vpp = HostingEnvironment.VirtualPathProvider;
  71. this.virtualPath = virtualPath;
  72. this.virtualPathDirectory = VirtualPathUtility.GetDirectory (virtualPath.Absolute);
  73. if (HttpRuntime.CaseInsensitive) {
  74. this.stringComparer = StringComparison.OrdinalIgnoreCase;
  75. this.dictionaryComparer = StringComparer.OrdinalIgnoreCase;
  76. } else {
  77. this.stringComparer = StringComparison.Ordinal;
  78. this.dictionaryComparer = StringComparer.Ordinal;
  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. return GetSingleBuildProviderGroup (themebp);
  87. }
  88. CompilationSection section = CompilationSection;
  89. BuildProviderCollection bpcoll = section != null ? section.BuildProviders : null;
  90. if (bpcoll == null || bpcoll.Count == 0)
  91. return null;
  92. if (virtualPath.IsFake) {
  93. BuildProvider bp = GetBuildProvider (virtualPath, bpcoll);
  94. if (bp == null)
  95. return null;
  96. return GetSingleBuildProviderGroup (bp);
  97. }
  98. if (single) {
  99. AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
  100. } else {
  101. var cache = new Dictionary <string, bool> (dictionaryComparer);
  102. AddVirtualDir (GetVirtualDirectory (virtualPath.Absolute), bpcoll, cache);
  103. cache = null;
  104. if (buildProviders == null || buildProviders.Count == 0)
  105. AddVirtualFile (GetVirtualFile (virtualPath.Absolute), bpcoll);
  106. }
  107. if (buildProviders == null || buildProviders.Count == 0)
  108. return null;
  109. var buildProviderGroups = new List <BuildProviderGroup> ();
  110. foreach (BuildProvider bp in buildProviders.Values)
  111. AssignToGroup (bp, buildProviderGroups);
  112. if (buildProviderGroups == null || buildProviderGroups.Count == 0) {
  113. buildProviderGroups = null;
  114. return null;
  115. }
  116. // We need to reverse the order, so that the build happens from the least
  117. // dependant assemblies to the most dependant ones, more or less.
  118. buildProviderGroups.Reverse ();
  119. return buildProviderGroups;
  120. }
  121. bool AddBuildProvider (BuildProvider buildProvider)
  122. {
  123. if (buildProviders == null)
  124. buildProviders = new Dictionary <string, BuildProvider> (dictionaryComparer);
  125. string bpPath = buildProvider.VirtualPath;
  126. if (buildProviders.ContainsKey (bpPath))
  127. return false;
  128. buildProviders.Add (bpPath, buildProvider);
  129. return true;
  130. }
  131. void AddVirtualDir (VirtualDirectory vdir, BuildProviderCollection bpcoll, Dictionary <string, bool> cache)
  132. {
  133. if (vdir == null)
  134. return;
  135. BuildProvider bp;
  136. IDictionary <string, bool> deps;
  137. var dirs = new List <string> ();
  138. string fileVirtualPath;
  139. foreach (VirtualFile file in vdir.Files) {
  140. fileVirtualPath = file.VirtualPath;
  141. if (BuildManager.IgnoreVirtualPath (fileVirtualPath))
  142. continue;
  143. bp = GetBuildProvider (fileVirtualPath, bpcoll);
  144. if (bp == null)
  145. continue;
  146. if (!AddBuildProvider (bp))
  147. continue;
  148. deps = bp.ExtractDependencies ();
  149. if (deps == null)
  150. continue;
  151. string depDir, s;
  152. dirs.Clear ();
  153. foreach (var dep in deps) {
  154. s = dep.Key;
  155. depDir = VirtualPathUtility.GetDirectory (s); // dependencies are assumed to contain absolute paths
  156. if (cache.ContainsKey (depDir))
  157. continue;
  158. AddVirtualDir (GetVirtualDirectory (s), bpcoll, cache);
  159. }
  160. }
  161. }
  162. void AddVirtualFile (VirtualFile file, BuildProviderCollection bpcoll)
  163. {
  164. if (file == null || BuildManager.IgnoreVirtualPath (file.VirtualPath))
  165. return;
  166. BuildProvider bp = GetBuildProvider (file.VirtualPath, bpcoll);
  167. if (bp == null)
  168. return;
  169. AddBuildProvider (bp);
  170. }
  171. List <BuildProviderGroup> GetSingleBuildProviderGroup (BuildProvider bp)
  172. {
  173. var ret = new List <BuildProviderGroup> ();
  174. var group = new BuildProviderGroup ();
  175. group.AddProvider (bp);
  176. ret.Add (group);
  177. return ret;
  178. }
  179. VirtualDirectory GetVirtualDirectory (string virtualPath)
  180. {
  181. if (!vpp.DirectoryExists (VirtualPathUtility.GetDirectory (virtualPath)))
  182. return null;
  183. return vpp.GetDirectory (virtualPath);
  184. }
  185. VirtualFile GetVirtualFile (string virtualPath)
  186. {
  187. if (!vpp.FileExists (virtualPath))
  188. return null;
  189. return vpp.GetFile (virtualPath);
  190. }
  191. Type GetBuildProviderCodeDomType (BuildProvider bp)
  192. {
  193. CompilerType ct = bp.CodeCompilerType;
  194. if (ct == null) {
  195. string language = bp.LanguageName;
  196. if (String.IsNullOrEmpty (language))
  197. language = CompilationSection.DefaultLanguage;
  198. ct = BuildManager.GetDefaultCompilerTypeForLanguage (language, CompilationSection, false);
  199. }
  200. Type ret = ct != null ? ct.CodeDomProviderType : null;
  201. if (ret == null)
  202. throw new HttpException ("Unable to determine code compilation language provider for virtual path '" + bp.VirtualPath + "'.");
  203. return ret;
  204. }
  205. void AssignToGroup (BuildProvider buildProvider, List <BuildProviderGroup> groups)
  206. {
  207. if (IsDependencyCycle (buildProvider))
  208. throw new HttpException ("Dependency cycles are not suppported: " + buildProvider.VirtualPath);
  209. BuildProviderGroup myGroup = null;
  210. string bpVirtualPath = buildProvider.VirtualPath;
  211. string bpPath = VirtualPathUtility.GetDirectory (bpVirtualPath);
  212. bool canAdd;
  213. if (BuildManager.HasCachedItemNoLock (buildProvider.VirtualPath))
  214. return;
  215. if (buildProvider is ApplicationFileBuildProvider || buildProvider is ThemeDirectoryBuildProvider) {
  216. // global.asax and theme directory go into their own assemblies
  217. myGroup = new BuildProviderGroup ();
  218. myGroup.Standalone = true;
  219. InsertGroup (myGroup, groups);
  220. } else {
  221. Type bpCodeDomType = GetBuildProviderCodeDomType (buildProvider);
  222. foreach (BuildProviderGroup group in groups) {
  223. if (group.Standalone)
  224. continue;
  225. if (group.Count == 0) {
  226. myGroup = group;
  227. break;
  228. }
  229. canAdd = true;
  230. foreach (BuildProvider bp in group) {
  231. if (IsDependency (buildProvider, bp)) {
  232. canAdd = false;
  233. break;
  234. }
  235. // There should be one assembly per virtual dir
  236. if (String.Compare (bpPath, VirtualPathUtility.GetDirectory (bp.VirtualPath), stringComparer) != 0) {
  237. canAdd = false;
  238. break;
  239. }
  240. // Different languages go to different assemblies
  241. if (bpCodeDomType != null) {
  242. Type type = GetBuildProviderCodeDomType (bp);
  243. if (type != null) {
  244. if (type != bpCodeDomType) {
  245. canAdd = false;
  246. break;
  247. }
  248. }
  249. }
  250. }
  251. if (!canAdd)
  252. continue;
  253. myGroup = group;
  254. break;
  255. }
  256. if (myGroup == null) {
  257. myGroup = new BuildProviderGroup ();
  258. InsertGroup (myGroup, groups);
  259. }
  260. }
  261. myGroup.AddProvider (buildProvider);
  262. if (String.Compare (bpPath, virtualPathDirectory, stringComparer) == 0)
  263. myGroup.Master = true;
  264. }
  265. void InsertGroup (BuildProviderGroup group, List <BuildProviderGroup> groups)
  266. {
  267. if (group.Application) {
  268. groups.Insert (groups.Count - 1, group);
  269. return;
  270. }
  271. int index;
  272. if (group.Standalone)
  273. index = groups.FindLastIndex (SkipApplicationGroup);
  274. else
  275. index = groups.FindLastIndex (SkipStandaloneGroups);
  276. if (index == -1)
  277. groups.Add (group);
  278. else
  279. groups.Insert (index == 0 ? 0 : index - 1, group);
  280. }
  281. static bool SkipStandaloneGroups (BuildProviderGroup group)
  282. {
  283. if (group == null)
  284. return false;
  285. return group.Standalone;
  286. }
  287. static bool SkipApplicationGroup (BuildProviderGroup group)
  288. {
  289. if (group == null)
  290. return false;
  291. return group.Application;
  292. }
  293. bool IsDependency (BuildProvider bp1, BuildProvider bp2)
  294. {
  295. IDictionary <string, bool> deps = bp1.ExtractDependencies ();
  296. if (deps == null)
  297. return false;
  298. if (deps.ContainsKey (bp2.VirtualPath))
  299. return true;
  300. BuildProvider bp;
  301. // It won't loop forever as by the time this method is called, we are sure there are no cycles
  302. foreach (var dep in deps) {
  303. if (!buildProviders.TryGetValue (dep.Key, out bp))
  304. continue;
  305. if (IsDependency (bp, bp2))
  306. return true;
  307. }
  308. return false;
  309. }
  310. bool IsDependencyCycle (BuildProvider buildProvider)
  311. {
  312. var cache = new Dictionary <BuildProvider, bool> ();
  313. cache.Add (buildProvider, true);
  314. return IsDependencyCycle (cache, buildProvider.ExtractDependencies ());
  315. }
  316. bool IsDependencyCycle (Dictionary <BuildProvider, bool> cache, IDictionary <string, bool> deps)
  317. {
  318. if (deps == null)
  319. return false;
  320. BuildProvider bp;
  321. foreach (var d in deps) {
  322. if (!buildProviders.TryGetValue (d.Key, out bp))
  323. continue;
  324. if (cache.ContainsKey (bp))
  325. return true;
  326. cache.Add (bp, true);
  327. if (IsDependencyCycle (cache, bp.ExtractDependencies ()))
  328. return true;
  329. cache.Remove (bp);
  330. }
  331. return false;
  332. }
  333. public static BuildProvider GetBuildProvider (string virtualPath, BuildProviderCollection coll)
  334. {
  335. return GetBuildProvider (new VirtualPath (virtualPath), coll);
  336. }
  337. public static BuildProvider GetBuildProvider (VirtualPath virtualPath, BuildProviderCollection coll)
  338. {
  339. if (virtualPath == null || String.IsNullOrEmpty (virtualPath.Original) || coll == null)
  340. return null;
  341. string extension = virtualPath.Extension;
  342. BuildProvider bp = coll.GetProviderForExtension (extension);
  343. if (bp == null) {
  344. if (String.Compare (extension, ".asax", StringComparison.OrdinalIgnoreCase) == 0)
  345. bp = new ApplicationFileBuildProvider ();
  346. else if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/"))
  347. bp = new ThemeDirectoryBuildProvider ();
  348. if (bp != null)
  349. bp.SetVirtualPath (virtualPath);
  350. return bp;
  351. }
  352. object[] attrs = bp.GetType ().GetCustomAttributes (typeof (BuildProviderAppliesToAttribute), true);
  353. if (attrs == null || attrs.Length == 0)
  354. return bp;
  355. BuildProviderAppliesTo appliesTo = ((BuildProviderAppliesToAttribute)attrs [0]).AppliesTo;
  356. if ((appliesTo & BuildProviderAppliesTo.Web) == 0)
  357. return null;
  358. bp.SetVirtualPath (virtualPath);
  359. return bp;
  360. }
  361. }
  362. }