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