AssemblyBuilder.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. //
  2. // System.Web.Compilation.AssemblyBuilder
  3. //
  4. // Authors:
  5. // Chris Toshok ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  7. // Marek Habersack ([email protected])
  8. //
  9. // (C) 2006-2008 Novell, Inc (http://www.novell.com)
  10. //
  11. //
  12. // Permission is hereby granted, free of charge, to any person obtaining
  13. // a copy of this software and associated documentation files (the
  14. // "Software"), to deal in the Software without restriction, including
  15. // without limitation the rights to use, copy, modify, merge, publish,
  16. // distribute, sublicense, and/or sell copies of the Software, and to
  17. // permit persons to whom the Software is furnished to do so, subject to
  18. // the following conditions:
  19. //
  20. // The above copyright notice and this permission notice shall be
  21. // included in all copies or substantial portions of the Software.
  22. //
  23. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30. //
  31. #if NET_2_0
  32. using System;
  33. using System.CodeDom;
  34. using System.CodeDom.Compiler;
  35. using System.Collections.Generic;
  36. using System.Collections.Specialized;
  37. using System.IO;
  38. using System.Reflection;
  39. using System.Web.Configuration;
  40. using System.Web.Util;
  41. using System.Web.Hosting;
  42. namespace System.Web.Compilation {
  43. internal class CompileUnitPartialType
  44. {
  45. public readonly CodeCompileUnit Unit;
  46. public readonly CodeNamespace ParentNamespace;
  47. public readonly CodeTypeDeclaration PartialType;
  48. string typeName;
  49. public string TypeName {
  50. get {
  51. if (typeName == null) {
  52. if (ParentNamespace == null || PartialType == null)
  53. return null;
  54. typeName = ParentNamespace.Name;
  55. if (String.IsNullOrEmpty (typeName))
  56. typeName = PartialType.Name;
  57. else
  58. typeName += "." + PartialType.Name;
  59. }
  60. return typeName;
  61. }
  62. }
  63. public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
  64. {
  65. this.Unit = unit;
  66. this.ParentNamespace = parentNamespace;
  67. this.PartialType = type;
  68. }
  69. }
  70. public class AssemblyBuilder {
  71. const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
  72. const int COPY_BUFFER_SIZE = 8192;
  73. static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
  74. CodeDomProvider provider;
  75. CompilerParameters parameters;
  76. Dictionary <string, bool> code_files;
  77. Dictionary <string, List <CompileUnitPartialType>> partial_types;
  78. List <CodeCompileUnit> units;
  79. List <string> source_files;
  80. List <Assembly> referenced_assemblies;
  81. Dictionary <string, string> resource_files;
  82. TempFileCollection temp_files;
  83. string outputFilesPrefix;
  84. string outputAssemblyPrefix;
  85. string outputAssemblyName;
  86. internal AssemblyBuilder (CodeDomProvider provider)
  87. : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
  88. {}
  89. internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
  90. : this (null, provider, assemblyBaseName)
  91. {}
  92. internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
  93. : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
  94. {}
  95. internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
  96. {
  97. this.provider = provider;
  98. this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
  99. units = new List <CodeCompileUnit> ();
  100. CompilationSection section;
  101. if (virtualPath != null)
  102. section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Absolute);
  103. else
  104. section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
  105. string tempdir = section.TempDirectory;
  106. if (String.IsNullOrEmpty (tempdir))
  107. tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
  108. if (!KeepFiles)
  109. KeepFiles = section.Debug;
  110. temp_files = new TempFileCollection (tempdir, KeepFiles);
  111. }
  112. internal string OutputFilesPrefix {
  113. get {
  114. if (outputFilesPrefix == null)
  115. outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
  116. return outputFilesPrefix;
  117. }
  118. set {
  119. if (String.IsNullOrEmpty (value))
  120. outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
  121. else
  122. outputFilesPrefix = value;
  123. outputAssemblyPrefix = null;
  124. outputAssemblyName = null;
  125. }
  126. }
  127. internal string OutputAssemblyPrefix {
  128. get {
  129. if (outputAssemblyPrefix == null) {
  130. string basePath = temp_files.BasePath;
  131. string baseName = Path.GetFileName (basePath);
  132. string baseDir = Path.GetDirectoryName (basePath);
  133. outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
  134. }
  135. return outputAssemblyPrefix;
  136. }
  137. }
  138. internal string OutputAssemblyName {
  139. get {
  140. if (outputAssemblyName == null)
  141. outputAssemblyName = OutputAssemblyPrefix + ".dll";
  142. return outputAssemblyName;
  143. }
  144. }
  145. internal TempFileCollection TempFiles {
  146. get { return temp_files; }
  147. }
  148. internal CompilerParameters CompilerOptions {
  149. get { return parameters; }
  150. set { parameters = value; }
  151. }
  152. internal CodeCompileUnit [] GetUnitsAsArray ()
  153. {
  154. CodeCompileUnit [] result = new CodeCompileUnit [units.Count];
  155. units.CopyTo (result, 0);
  156. return result;
  157. }
  158. internal List <CodeCompileUnit> Units {
  159. get {
  160. if (units == null)
  161. units = new List <CodeCompileUnit> ();
  162. return units;
  163. }
  164. }
  165. internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
  166. get {
  167. if (partial_types == null)
  168. partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
  169. return partial_types;
  170. }
  171. }
  172. Dictionary <string, bool> CodeFiles {
  173. get {
  174. if (code_files == null)
  175. code_files = new Dictionary <string, bool> ();
  176. return code_files;
  177. }
  178. }
  179. List <string> SourceFiles {
  180. get {
  181. if (source_files == null)
  182. source_files = new List <string> ();
  183. return source_files;
  184. }
  185. }
  186. Dictionary <string, string> ResourceFiles {
  187. get {
  188. if (resource_files == null)
  189. resource_files = new Dictionary <string, string> ();
  190. return resource_files;
  191. }
  192. }
  193. public void AddAssemblyReference (Assembly a)
  194. {
  195. if (a == null)
  196. throw new ArgumentNullException ("a");
  197. List <Assembly> assemblies = ReferencedAssemblies;
  198. if (assemblies.Contains (a))
  199. return;
  200. assemblies.Add (a);
  201. }
  202. internal void AddAssemblyReference (string assemblyLocation)
  203. {
  204. try {
  205. Assembly asm = Assembly.LoadFrom (assemblyLocation);
  206. if (asm == null)
  207. return;
  208. AddAssemblyReference (asm);
  209. } catch {
  210. // ignore, it will come up later
  211. }
  212. }
  213. internal void AddAssemblyReference (List <Assembly> asmlist)
  214. {
  215. if (asmlist == null)
  216. return;
  217. foreach (Assembly a in asmlist) {
  218. if (a == null)
  219. continue;
  220. AddAssemblyReference (a);
  221. }
  222. }
  223. internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
  224. {
  225. if (compileUnit == null)
  226. throw new ArgumentNullException ("compileUnit");
  227. units.Add (CheckForPartialTypes (compileUnit));
  228. }
  229. public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
  230. {
  231. if (buildProvider == null)
  232. throw new ArgumentNullException ("buildProvider");
  233. if (compileUnit == null)
  234. throw new ArgumentNullException ("compileUnit");
  235. units.Add (CheckForPartialTypes (compileUnit));
  236. }
  237. public TextWriter CreateCodeFile (BuildProvider buildProvider)
  238. {
  239. if (buildProvider == null)
  240. throw new ArgumentNullException ("buildProvider");
  241. // Generate a file name with the correct source language extension
  242. string filename = GetTempFilePhysicalPath (provider.FileExtension);
  243. SourceFiles.Add (filename);
  244. return new StreamWriter (File.OpenWrite (filename));
  245. }
  246. internal void AddCodeFile (string path)
  247. {
  248. AddCodeFile (path, null, false);
  249. }
  250. internal void AddCodeFile (string path, BuildProvider bp)
  251. {
  252. AddCodeFile (path, bp, false);
  253. }
  254. internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
  255. {
  256. if (String.IsNullOrEmpty (path))
  257. return;
  258. Dictionary <string, bool> codeFiles = CodeFiles;
  259. if (codeFiles.ContainsKey (path))
  260. return;
  261. codeFiles.Add (path, true);
  262. string extension = Path.GetExtension (path);
  263. if (extension == null || extension.Length == 0)
  264. return; // maybe better to throw an exception here?
  265. extension = extension.Substring (1);
  266. string filename = GetTempFilePhysicalPath (extension);
  267. if (isVirtual) {
  268. VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
  269. if (vf == null)
  270. throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
  271. using (FileStream f = new FileStream (filename, FileMode.Create, FileAccess.Write)) {
  272. using (Stream s = vf.Open ()) {
  273. byte[] input = new byte [COPY_BUFFER_SIZE];
  274. int retval;
  275. while ((retval = s.Read (input, 0, COPY_BUFFER_SIZE)) > 0)
  276. f.Write (input, 0, retval);
  277. }
  278. }
  279. } else
  280. File.Copy (path, filename);
  281. SourceFiles.Add (filename);
  282. }
  283. public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
  284. {
  285. if (buildProvider == null)
  286. throw new ArgumentNullException ("buildProvider");
  287. if (name == null || name == "")
  288. throw new ArgumentNullException ("name");
  289. string filename = GetTempFilePhysicalPath ("resource");
  290. Stream stream = File.OpenWrite (filename);
  291. ResourceFiles [name] = filename;
  292. return stream;
  293. }
  294. [MonoTODO ("Not implemented, does nothing")]
  295. public void GenerateTypeFactory (string typeName)
  296. {
  297. // Do nothing by now.
  298. }
  299. public string GetTempFilePhysicalPath (string extension)
  300. {
  301. if (extension == null)
  302. throw new ArgumentNullException ("extension");
  303. string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
  304. temp_files.AddFile (newFileName, KeepFiles);
  305. return newFileName;
  306. }
  307. public CodeDomProvider CodeDomProvider {
  308. get { return provider; }
  309. }
  310. List <Assembly> ReferencedAssemblies {
  311. get {
  312. if (referenced_assemblies == null)
  313. referenced_assemblies = new List <Assembly> ();
  314. return referenced_assemblies;
  315. }
  316. }
  317. CodeCompileUnit CheckForPartialTypes (CodeCompileUnit compileUnit)
  318. {
  319. if (compileUnit == null)
  320. return null;
  321. CodeTypeDeclarationCollection types;
  322. CompileUnitPartialType partialType;
  323. string partialTypeName;
  324. List <CompileUnitPartialType> tmp;
  325. Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
  326. foreach (CodeNamespace ns in compileUnit.Namespaces) {
  327. if (ns == null)
  328. continue;
  329. types = ns.Types;
  330. if (types == null || types.Count == 0)
  331. continue;
  332. foreach (CodeTypeDeclaration type in types) {
  333. if (type == null)
  334. continue;
  335. if (type.IsPartial) {
  336. partialType = new CompileUnitPartialType (compileUnit, ns, type);
  337. partialTypeName = partialType.TypeName;
  338. if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
  339. tmp = new List <CompileUnitPartialType> (1);
  340. partialTypes.Add (partialTypeName, tmp);
  341. }
  342. tmp.Add (partialType);
  343. }
  344. }
  345. }
  346. return compileUnit;
  347. }
  348. void ProcessPartialTypes ()
  349. {
  350. Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
  351. if (partialTypes.Count == 0)
  352. return;
  353. foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
  354. ProcessType (kvp.Value);
  355. }
  356. void ProcessType (List <CompileUnitPartialType> typeList)
  357. {
  358. CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
  359. int counter = 0;
  360. foreach (CompileUnitPartialType type in typeList) {
  361. if (counter == 0) {
  362. types [0] = type;
  363. counter++;
  364. continue;
  365. }
  366. for (int i = 0; i < counter; i++)
  367. CompareTypes (types [i], type);
  368. types [counter++] = type;
  369. }
  370. }
  371. void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
  372. {
  373. CodeTypeDeclaration sourceType = source.PartialType;
  374. CodeTypeMemberCollection targetMembers = target.PartialType.Members;
  375. List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
  376. foreach (CodeTypeMember member in targetMembers) {
  377. if (TypeHasMember (sourceType, member))
  378. membersToRemove.Add (member);
  379. }
  380. foreach (CodeTypeMember member in membersToRemove)
  381. targetMembers.Remove (member);
  382. }
  383. bool TypeHasMember (CodeTypeDeclaration type, CodeMemberMethod member)
  384. {
  385. if (type == null || member == null)
  386. return false;
  387. CodeMemberMethod method = FindMemberByName (type, member.Name) as CodeMemberMethod;
  388. if (method == null)
  389. return false;
  390. if (method.Parameters.Count != member.Parameters.Count)
  391. return false;
  392. return true;
  393. }
  394. bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
  395. {
  396. if (type == null || member == null)
  397. return false;
  398. return (FindMemberByName (type, member.Name) != null);
  399. }
  400. CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
  401. {
  402. foreach (CodeTypeMember m in type.Members) {
  403. if (m == null || m.Name != name)
  404. continue;
  405. return m;
  406. }
  407. return null;
  408. }
  409. internal CompilerResults BuildAssembly (VirtualPath virtualPath)
  410. {
  411. return BuildAssembly (virtualPath, CompilerOptions);
  412. }
  413. internal CompilerResults BuildAssembly (CompilerParameters options)
  414. {
  415. return BuildAssembly (null, options);
  416. }
  417. internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
  418. {
  419. if (options == null)
  420. throw new ArgumentNullException ("options");
  421. options.TempFiles = temp_files;
  422. if (options.OutputAssembly == null)
  423. options.OutputAssembly = OutputAssemblyName;
  424. ProcessPartialTypes ();
  425. CompilerResults results;
  426. CodeCompileUnit [] units = GetUnitsAsArray ();
  427. // Since we may have some source files and some code
  428. // units, we generate code from all of them and then
  429. // compile the assembly from the set of temporary source
  430. // files. This also facilates possible debugging for the
  431. // end user, since they get the code beforehand.
  432. List <string> files = SourceFiles;
  433. Dictionary <string, string> resources = ResourceFiles;
  434. if (units.Length == 0 && files.Count == 0 && resources.Count == 0 && options.EmbeddedResources.Count == 0)
  435. return null;
  436. string filename;
  437. StreamWriter sw = null;
  438. foreach (CodeCompileUnit unit in units) {
  439. filename = GetTempFilePhysicalPath (provider.FileExtension);
  440. try {
  441. sw = new StreamWriter (File.OpenWrite (filename));
  442. provider.GenerateCodeFromCompileUnit (unit, sw, null);
  443. files.Add (filename);
  444. } catch {
  445. throw;
  446. } finally {
  447. if (sw != null) {
  448. sw.Flush ();
  449. sw.Close ();
  450. }
  451. }
  452. }
  453. foreach (KeyValuePair <string, string> de in resources)
  454. options.EmbeddedResources.Add (de.Value);
  455. foreach (Assembly refasm in ReferencedAssemblies)
  456. options.ReferencedAssemblies.Add (refasm.Location);
  457. results = provider.CompileAssemblyFromFile (options, files.ToArray ());
  458. if (results.NativeCompilerReturnValue != 0) {
  459. string fileText = null;
  460. try {
  461. using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
  462. fileText = sr.ReadToEnd ();
  463. }
  464. } catch (Exception) {}
  465. #if DEBUG
  466. Console.WriteLine ("Compilation failed. Errors:");
  467. foreach (CompilerError err in results.Errors)
  468. Console.WriteLine (err);
  469. #endif
  470. throw new CompilationException (virtualPath.Original, results, fileText);
  471. }
  472. Assembly assembly = results.CompiledAssembly;
  473. if (assembly == null) {
  474. if (!File.Exists (options.OutputAssembly)) {
  475. results.TempFiles.Delete ();
  476. throw new CompilationException (virtualPath.Original, results.Errors,
  477. "No assembly returned after compilation!?");
  478. }
  479. try {
  480. results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
  481. } catch (Exception ex) {
  482. results.TempFiles.Delete ();
  483. throw new HttpException ("Unable to load compiled assembly", ex);
  484. }
  485. }
  486. if (!KeepFiles)
  487. results.TempFiles.Delete ();
  488. return results;
  489. }
  490. }
  491. }
  492. #endif