2008-01-24 Marek Habersack <mhabersack@novell.com>
[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.Generic;
39 using System.Collections.Specialized;
40 using System.IO;
41 using System.Reflection;
42 using System.Web.Configuration;
43 using System.Web.Util;
44
45 namespace System.Web.Compilation {
46         internal class CompileUnitPartialType
47         {
48                 public readonly CodeCompileUnit Unit;
49                 public readonly CodeNamespace ParentNamespace;
50                 public readonly CodeTypeDeclaration PartialType;
51
52                 string typeName;
53                 
54                 public string TypeName {
55                         get {
56                                 if (typeName == null) {
57                                         if (ParentNamespace == null || PartialType == null)
58                                                 return null;
59                                         
60                                         typeName = ParentNamespace.Name;
61                                         if (String.IsNullOrEmpty (typeName))
62                                                 typeName = PartialType.Name;
63                                         else
64                                                 typeName += "." + PartialType.Name;
65                                 }
66
67                                 return typeName;
68                         }
69                 }
70                 
71                 public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
72                 {
73                         this.Unit = unit;
74                         this.ParentNamespace = parentNamespace;
75                         this.PartialType = type;
76                 }
77         }
78         
79         public class AssemblyBuilder {
80                 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
81                 
82                 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
83                 
84                 CodeDomProvider provider;
85                 CompilerParameters parameters;
86
87                 Dictionary <string, bool> code_files;
88                 Dictionary <string, List <CompileUnitPartialType>> partial_types;
89                 List <CodeCompileUnit> units;
90                 List <string> source_files;
91                 List <Assembly> referenced_assemblies;
92                 Dictionary <string, string> resource_files;
93                 TempFileCollection temp_files;
94                 string outputFilesPrefix;
95                 string outputAssemblyPrefix;
96                 string outputAssemblyName;
97                 
98                 internal AssemblyBuilder (CodeDomProvider provider)
99                 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
100                 {}
101
102                 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
103                 : this (null, provider, assemblyBaseName)
104                 {}
105
106                 internal AssemblyBuilder (string virtualPath, CodeDomProvider provider)
107                 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
108                 {}
109                 
110                 internal AssemblyBuilder (string virtualPath, CodeDomProvider provider, string assemblyBaseName)
111                 {
112                         this.provider = provider;
113                         this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
114                         
115                         units = new List <CodeCompileUnit> ();
116
117                         CompilationSection section;
118                         if (virtualPath != null)
119                                 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath);
120                         else
121                                 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
122                         string tempdir = section.TempDirectory;
123                         if (String.IsNullOrEmpty (tempdir))
124                                 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
125
126                         if (!KeepFiles)
127                                 KeepFiles = section.Debug;
128                         
129                         temp_files = new TempFileCollection (tempdir, KeepFiles);
130                 }
131
132                 internal string OutputFilesPrefix {
133                         get {
134                                 if (outputFilesPrefix == null)
135                                         outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
136
137                                 return outputFilesPrefix;
138                         }
139
140                         set {
141                                 if (String.IsNullOrEmpty (value))
142                                         outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
143                                 else
144                                         outputFilesPrefix = value;
145                                 outputAssemblyPrefix = null;
146                                 outputAssemblyName = null;
147                         }
148                 }
149                 
150                 internal string OutputAssemblyPrefix {
151                         get {
152                                 if (outputAssemblyPrefix == null) {
153                                         string basePath = temp_files.BasePath;
154                                         string baseName = Path.GetFileName (basePath);
155                                         string baseDir = Path.GetDirectoryName (basePath);
156
157                                         outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
158                                 }
159
160                                 return outputAssemblyPrefix;
161                         }
162                 }
163
164                 internal string OutputAssemblyName {
165                         get {
166                                 if (outputAssemblyName == null)
167                                         outputAssemblyName = OutputAssemblyPrefix + ".dll";
168
169                                 return outputAssemblyName;
170                         }
171                 }
172                 
173                 internal TempFileCollection TempFiles {
174                         get { return temp_files; }
175                 }
176
177                 internal CompilerParameters CompilerOptions {
178                         get { return parameters; }
179                         set { parameters = value; }
180                 }
181                 
182                 internal CodeCompileUnit [] GetUnitsAsArray ()
183                 {
184                         CodeCompileUnit [] result = new CodeCompileUnit [units.Count];
185                         units.CopyTo (result, 0);
186                         return result;
187                 }
188
189                 internal List <CodeCompileUnit> Units {
190                         get {
191                                 if (units == null)
192                                         units = new List <CodeCompileUnit> ();
193                                 return units;
194                         }
195                 }
196                 
197                 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
198                         get {
199                                 if (partial_types == null)
200                                         partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
201                                 return partial_types;
202                         }
203                 }
204                 
205                 Dictionary <string, bool> CodeFiles {
206                         get {
207                                 if (code_files == null)
208                                         code_files = new Dictionary <string, bool> ();
209                                 return code_files;
210                         }
211                 }
212                 
213                 List <string> SourceFiles {
214                         get {
215                                 if (source_files == null)
216                                         source_files = new List <string> ();
217                                 return source_files;
218                         }
219                 }
220
221                 Dictionary <string, string> ResourceFiles {
222                         get {
223                                 if (resource_files == null)
224                                         resource_files = new Dictionary <string, string> ();
225                                 return resource_files;
226                         }
227                 }
228
229                 public void AddAssemblyReference (Assembly a)
230                 {
231                         if (a == null)
232                                 throw new ArgumentNullException ("a");
233
234                         List <Assembly> assemblies = ReferencedAssemblies;
235                         
236                         if (assemblies.Contains (a))
237                                 return;
238                         
239                         assemblies.Add (a);
240                 }
241
242                 internal void AddAssemblyReference (List <Assembly> asmlist)
243                 {
244                         if (asmlist == null)
245                                 return;
246                         
247                         foreach (Assembly a in asmlist) {
248                                 if (a == null)
249                                         continue;
250
251                                 AddAssemblyReference (a);
252                         }
253                 }
254                 
255                 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
256                 {
257                         if (compileUnit == null)
258                                 throw new ArgumentNullException ("compileUnit");
259                         units.Add (CheckForPartialTypes (compileUnit));
260                 }
261                 
262                 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
263                 {
264                         if (buildProvider == null)
265                                 throw new ArgumentNullException ("buildProvider");
266
267                         if (compileUnit == null)
268                                 throw new ArgumentNullException ("compileUnit");
269
270                         units.Add (CheckForPartialTypes (compileUnit));
271                 }
272
273                 public TextWriter CreateCodeFile (BuildProvider buildProvider)
274                 {
275                         if (buildProvider == null)
276                                 throw new ArgumentNullException ("buildProvider");
277
278                         // Generate a file name with the correct source language extension
279                         string filename = GetTempFilePhysicalPath (provider.FileExtension);
280                         SourceFiles.Add (filename);
281                         return new StreamWriter (File.OpenWrite (filename), WebEncoding.FileEncoding);
282                 }
283
284                 internal void AddCodeFile (string path)
285                 {
286                         AddCodeFile (path, null);
287                 }
288                 
289                 internal void AddCodeFile (string path, BuildProvider bp)
290                 {
291                         if (String.IsNullOrEmpty (path))
292                                 return;
293
294                         Dictionary <string, bool> codeFiles = CodeFiles;
295                         if (codeFiles.ContainsKey (path))
296                                 return;
297                         
298                         codeFiles.Add (path, true);
299                         
300                         string extension = Path.GetExtension (path);
301                         if (extension == null || extension.Length == 0)
302                                 return; // maybe better to throw an exception here?
303                         extension = extension.Substring (1);
304                         string filename = GetTempFilePhysicalPath (extension);
305                         File.Copy (path, filename, true);
306                         SourceFiles.Add (filename);
307                 }
308                 
309                 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
310                 {
311                         if (buildProvider == null)
312                                 throw new ArgumentNullException ("buildProvider");
313
314                         if (name == null || name == "")
315                                 throw new ArgumentNullException ("name");
316
317                         string filename = GetTempFilePhysicalPath ("resource");
318                         Stream stream = File.OpenWrite (filename);
319                         ResourceFiles [name] = filename;
320                         return stream;
321                 }
322
323                 [MonoTODO ("Not implemented, does nothing")]
324                 public void GenerateTypeFactory (string typeName)
325                 {
326                         // Do nothing by now.
327                 }
328
329                 public string GetTempFilePhysicalPath (string extension)
330                 {
331                         if (extension == null)
332                                 throw new ArgumentNullException ("extension");
333
334                         string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
335                         temp_files.AddFile (newFileName, KeepFiles);
336
337                         return newFileName;
338                 }
339
340                 public CodeDomProvider CodeDomProvider {
341                         get { return provider; }
342                 }
343
344                 List <Assembly> ReferencedAssemblies {
345                         get {
346                                 if (referenced_assemblies == null)
347                                         referenced_assemblies = new List <Assembly> ();
348
349                                 return referenced_assemblies;
350                         }
351                 }
352                 
353                 CodeCompileUnit CheckForPartialTypes (CodeCompileUnit compileUnit)
354                 {
355                         if (compileUnit == null)
356                                 return null;
357
358                         CodeTypeDeclarationCollection types;
359                         CompileUnitPartialType partialType;
360                         string partialTypeName;
361                         List <CompileUnitPartialType> tmp;
362                         Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
363                         
364                         foreach (CodeNamespace ns in compileUnit.Namespaces) {
365                                 if (ns == null)
366                                         continue;
367                                 types = ns.Types;
368                                 if (types == null || types.Count == 0)
369                                         continue;
370
371                                 foreach (CodeTypeDeclaration type in types) {
372                                         if (type == null)
373                                                 continue;
374
375                                         if (type.IsPartial) {
376                                                 partialType = new CompileUnitPartialType (compileUnit, ns, type);
377                                                 partialTypeName = partialType.TypeName;
378                                                 
379                                                 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
380                                                         tmp = new List <CompileUnitPartialType> (1);
381                                                         partialTypes.Add (partialTypeName, tmp);
382                                                 }
383                                                 tmp.Add (partialType);
384                                         }
385                                 }
386                         }
387                                                 
388                         return compileUnit;
389                 }
390                 
391                 void ProcessPartialTypes ()
392                 {
393                         Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
394                         if (partialTypes.Count == 0)
395                                 return;
396                         
397                         foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
398                                 ProcessType (kvp.Value);
399                 }
400
401                 void ProcessType (List <CompileUnitPartialType> typeList)
402                 {
403                         CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
404                         int counter = 0;
405                         
406                         foreach (CompileUnitPartialType type in typeList) {
407                                 if (counter == 0) {
408                                         types [0] = type;
409                                         counter++;
410                                         continue;
411                                 }
412
413                                 for (int i = 0; i < counter; i++)
414                                         CompareTypes (types [i], type);
415                                 types [counter++] = type;
416                         }
417                 }
418
419                 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
420                 {
421                         CodeTypeDeclaration sourceType = source.PartialType;
422                         CodeTypeMemberCollection targetMembers = target.PartialType.Members;
423                         List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
424                         
425                         foreach (CodeTypeMember member in targetMembers) {
426                                 if (TypeHasMember (sourceType, member))
427                                         membersToRemove.Add (member);
428                         }
429
430                         foreach (CodeTypeMember member in membersToRemove)
431                                 targetMembers.Remove (member);
432                 }               
433
434                 bool TypeHasMember (CodeTypeDeclaration type, CodeMemberMethod member)
435                 {
436                         if (type == null || member == null)
437                                 return false;
438
439                         CodeMemberMethod method = FindMemberByName (type, member.Name) as CodeMemberMethod;
440                         if (method == null)
441                                 return false;
442
443                         if (method.Parameters.Count != member.Parameters.Count)
444                                 return false;
445                         
446                         return true;
447                 }
448
449                 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
450                 {
451                         if (type == null || member == null)
452                                 return false;
453
454                         return (FindMemberByName (type, member.Name) != null);
455                 }
456
457                 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
458                 {
459                         foreach (CodeTypeMember m in type.Members) {
460                                 if (m == null || m.Name != name)
461                                         continue;
462                                 return m;
463                         }
464
465                         return null;
466                 }
467                 
468                 internal CompilerResults BuildAssembly (string virtualPath)
469                 {
470                         return BuildAssembly (virtualPath, CompilerOptions);
471                 }
472                 
473                 internal CompilerResults BuildAssembly (CompilerParameters options)
474                 {
475                         return BuildAssembly (null, options);
476                 }
477                 
478                 internal CompilerResults BuildAssembly (string virtualPath, CompilerParameters options)
479                 {
480                         if (options == null)
481                                 throw new ArgumentNullException ("options");
482
483                         options.TempFiles = temp_files;
484                         if (options.OutputAssembly == null)
485                                 options.OutputAssembly = OutputAssemblyName;
486
487                         ProcessPartialTypes ();
488                         
489                         CompilerResults results;
490                         CodeCompileUnit [] units = GetUnitsAsArray ();
491
492                         // Since we may have some source files and some code
493                         // units, we generate code from all of them and then
494                         // compile the assembly from the set of temporary source
495                         // files. This also facilates possible debugging for the
496                         // end user, since they get the code beforehand.
497                         List <string> files = SourceFiles;
498                         string filename;
499                         StreamWriter sw = null;
500                         foreach (CodeCompileUnit unit in units) {
501                                 filename = GetTempFilePhysicalPath (provider.FileExtension);
502                                 try {
503                                         sw = new StreamWriter (File.OpenWrite (filename), WebEncoding.FileEncoding);
504                                         provider.GenerateCodeFromCompileUnit (unit, sw, null);
505                                         files.Add (filename);
506                                 } catch {
507                                         throw;
508                                 } finally {
509                                         if (sw != null) {
510                                                 sw.Flush ();
511                                                 sw.Close ();
512                                         }
513                                 }
514                         }
515                         Dictionary <string, string> resources = ResourceFiles;
516                         foreach (KeyValuePair <string, string> de in resources)
517                                 options.EmbeddedResources.Add (de.Value);
518                         foreach (Assembly refasm in ReferencedAssemblies)
519                                 options.ReferencedAssemblies.Add (refasm.Location);
520                         
521                         results = provider.CompileAssemblyFromFile (options, files.ToArray ());
522
523                         if (results.NativeCompilerReturnValue != 0) {
524                                 string fileText = null;
525                                 try {
526                                         using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
527                                                 fileText = sr.ReadToEnd ();
528                                         }
529                                 } catch (Exception) {}
530
531                                 throw new CompilationException (virtualPath, results.Errors, fileText);
532                         }
533                         
534                         Assembly assembly = results.CompiledAssembly;
535                         if (assembly == null) {
536                                 if (!File.Exists (options.OutputAssembly)) {
537                                         results.TempFiles.Delete ();
538                                         throw new CompilationException (virtualPath, results.Errors,
539                                                 "No assembly returned after compilation!?");
540                                 }
541
542                                 try {
543                                         results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
544                                 } catch (Exception ex) {
545                                         results.TempFiles.Delete ();
546                                         throw new HttpException ("Unable to load compiled assembly", ex);
547                                 }
548                         }
549
550                         if (!KeepFiles)
551                                 results.TempFiles.Delete ();
552                         return results;
553                 }
554         }
555 }
556 #endif
557