Add a more functional (i.e. fewer-stubs) implementation of System.Data.Linq.
[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         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         {
84                 struct CodeUnit
85                 {
86                         public readonly BuildProvider BuildProvider;
87                         public readonly CodeCompileUnit Unit;
88
89                         public CodeUnit (BuildProvider bp, CodeCompileUnit unit)
90                         {
91                                 this.BuildProvider = bp;
92                                 this.Unit = unit;
93                         }
94                 }
95                 
96                 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
97                 const int COPY_BUFFER_SIZE = 8192;
98                 
99                 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
100                 
101                 CodeDomProvider provider;
102                 CompilerParameters parameters;
103
104                 Dictionary <string, bool> code_files;
105                 Dictionary <string, List <CompileUnitPartialType>> partial_types;
106                 Dictionary <string, BuildProvider> path_to_buildprovider;
107                 List <CodeUnit> units;
108                 List <string> source_files;
109                 List <Assembly> referenced_assemblies;
110                 Dictionary <string, string> resource_files;
111                 TempFileCollection temp_files;
112                 string outputFilesPrefix;
113                 string outputAssemblyPrefix;
114                 string outputAssemblyName;
115                 
116                 internal AssemblyBuilder (CodeDomProvider provider)
117                 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
118                 {}
119
120                 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
121                 : this (null, provider, assemblyBaseName)
122                 {}
123
124                 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
125                 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
126                 {}
127                 
128                 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
129                 {
130                         this.provider = provider;
131                         this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
132                         
133                         units = new List <CodeUnit> ();
134
135                         CompilationSection section;
136                         if (virtualPath != null)
137                                 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Absolute);
138                         else
139                                 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
140                         string tempdir = section.TempDirectory;
141                         if (String.IsNullOrEmpty (tempdir))
142                                 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
143
144                         if (!KeepFiles)
145                                 KeepFiles = section.Debug;
146                         
147                         temp_files = new TempFileCollection (tempdir, KeepFiles);
148                 }
149
150                 internal string OutputFilesPrefix {
151                         get {
152                                 if (outputFilesPrefix == null)
153                                         outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
154
155                                 return outputFilesPrefix;
156                         }
157
158                         set {
159                                 if (String.IsNullOrEmpty (value))
160                                         outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
161                                 else
162                                         outputFilesPrefix = value;
163                                 outputAssemblyPrefix = null;
164                                 outputAssemblyName = null;
165                         }
166                 }
167                 
168                 internal string OutputAssemblyPrefix {
169                         get {
170                                 if (outputAssemblyPrefix == null) {
171                                         string basePath = temp_files.BasePath;
172                                         string baseName = Path.GetFileName (basePath);
173                                         string baseDir = Path.GetDirectoryName (basePath);
174
175                                         outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
176                                 }
177
178                                 return outputAssemblyPrefix;
179                         }
180                 }
181
182                 internal string OutputAssemblyName {
183                         get {
184                                 if (outputAssemblyName == null)
185                                         outputAssemblyName = OutputAssemblyPrefix + ".dll";
186
187                                 return outputAssemblyName;
188                         }
189                 }
190                 
191                 internal TempFileCollection TempFiles {
192                         get { return temp_files; }
193                 }
194
195                 internal CompilerParameters CompilerOptions {
196                         get { return parameters; }
197                         set { parameters = value; }
198                 }
199                 
200                 CodeUnit[] GetUnitsAsArray ()
201                 {
202                         CodeUnit[] result = new CodeUnit [units.Count];
203                         units.CopyTo (result, 0);
204                         return result;
205                 }
206                 
207                 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
208                         get {
209                                 if (partial_types == null)
210                                         partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
211                                 return partial_types;
212                         }
213                 }
214                 
215                 Dictionary <string, bool> CodeFiles {
216                         get {
217                                 if (code_files == null)
218                                         code_files = new Dictionary <string, bool> ();
219                                 return code_files;
220                         }
221                 }
222                 
223                 List <string> SourceFiles {
224                         get {
225                                 if (source_files == null)
226                                         source_files = new List <string> ();
227                                 return source_files;
228                         }
229                 }
230
231                 Dictionary <string, string> ResourceFiles {
232                         get {
233                                 if (resource_files == null)
234                                         resource_files = new Dictionary <string, string> ();
235                                 return resource_files;
236                         }
237                 }
238
239                 internal BuildProvider GetBuildProviderForPhysicalFilePath (string path)
240                 {
241                         if (String.IsNullOrEmpty (path) || path_to_buildprovider == null || path_to_buildprovider.Count == 0)
242                                 return null;
243
244                         BuildProvider ret;
245                         if (path_to_buildprovider.TryGetValue (path, out ret))
246                                 return ret;
247
248                         return null;
249                 }
250                 
251                 public void AddAssemblyReference (Assembly a)
252                 {
253                         if (a == null)
254                                 throw new ArgumentNullException ("a");
255
256                         List <Assembly> assemblies = ReferencedAssemblies;
257                         
258                         if (assemblies.Contains (a))
259                                 return;
260                         
261                         assemblies.Add (a);
262                 }
263
264                 internal void AddAssemblyReference (string assemblyLocation)
265                 {
266                         try {
267                                 Assembly asm = Assembly.LoadFrom (assemblyLocation);
268                                 if (asm == null)
269                                         return;
270
271                                 AddAssemblyReference (asm);
272                         } catch {
273                                 // ignore, it will come up later
274                         }
275                 }
276
277                 internal void AddAssemblyReference (ICollection asmcoll)
278                 {
279                         if (asmcoll == null || asmcoll.Count == 0)
280                                 return;
281
282                         Assembly asm;
283                         foreach (object o in asmcoll) {
284                                 asm = o as Assembly;
285                                 if (asm == null)
286                                         continue;
287
288                                 AddAssemblyReference (asm);
289                         }
290                 }
291                 
292                 internal void AddAssemblyReference (List <Assembly> asmlist)
293                 {
294                         if (asmlist == null)
295                                 return;
296                         
297                         foreach (Assembly a in asmlist) {
298                                 if (a == null)
299                                         continue;
300
301                                 AddAssemblyReference (a);
302                         }
303                 }
304                 
305                 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
306                 {
307                         if (compileUnit == null)
308                                 throw new ArgumentNullException ("compileUnit");
309                         units.Add (CheckForPartialTypes (new CodeUnit (null, compileUnit)));
310                 }
311                                 
312                 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
313                 {
314                         if (buildProvider == null)
315                                 throw new ArgumentNullException ("buildProvider");
316
317                         if (compileUnit == null)
318                                 throw new ArgumentNullException ("compileUnit");
319
320                         units.Add (CheckForPartialTypes (new CodeUnit (buildProvider, compileUnit)));
321                 }
322
323                 void AddPathToBuilderMap (string path, BuildProvider bp)
324                 {
325                         if (path_to_buildprovider == null)
326                                 path_to_buildprovider = new Dictionary <string, BuildProvider> ();
327
328                         if (path_to_buildprovider.ContainsKey (path))
329                                 return;
330
331                         path_to_buildprovider.Add (path, bp);
332                 }
333                 
334                 public TextWriter CreateCodeFile (BuildProvider buildProvider)
335                 {
336                         if (buildProvider == null)
337                                 throw new ArgumentNullException ("buildProvider");
338
339                         // Generate a file name with the correct source language extension
340                         string filename = GetTempFilePhysicalPath (provider.FileExtension);
341                         SourceFiles.Add (filename);
342                         AddPathToBuilderMap (filename, buildProvider);
343                         return new StreamWriter (File.OpenWrite (filename));
344                 }
345
346                 internal void AddCodeFile (string path)
347                 {
348                         AddCodeFile (path, null, false);
349                 }
350
351                 internal void AddCodeFile (string path, BuildProvider bp)
352                 {
353                         AddCodeFile (path, bp, false);
354                 }
355                 
356                 internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
357                 {
358                         if (String.IsNullOrEmpty (path))
359                                 return;
360
361                         Dictionary <string, bool> codeFiles = CodeFiles;
362                         if (codeFiles.ContainsKey (path))
363                                 return;
364                         
365                         codeFiles.Add (path, true);
366                         
367                         string extension = Path.GetExtension (path);
368                         if (extension == null || extension.Length == 0)
369                                 return; // maybe better to throw an exception here?
370                         extension = extension.Substring (1);
371                         string filename = GetTempFilePhysicalPath (extension);
372
373                         if (isVirtual) {
374                                 VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
375                                 if (vf == null)
376                                         throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
377
378                                 CopyFile (vf.Open (), filename);
379                         } else
380                                 CopyFile (path, filename);
381
382                         if (bp != null)
383                                 AddPathToBuilderMap (filename, bp);
384                         
385                         SourceFiles.Add (filename);
386                 }
387
388                 void CopyFile (string input, string filename)
389                 {
390                         CopyFile (new FileStream (input, FileMode.Open, FileAccess.Read), filename);
391                 }
392                 
393                 void CopyFile (Stream input, string filename)
394                 {
395                         using (StreamWriter sw = new StreamWriter (new FileStream (filename, FileMode.Create, FileAccess.Write), Encoding.UTF8)) {
396                                 using (StreamReader sr = new StreamReader (input, WebEncoding.FileEncoding)) {
397                                         sw.Write (sr.ReadToEnd ());
398                                 }
399                         }
400                 }
401                 
402                 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
403                 {
404                         if (buildProvider == null)
405                                 throw new ArgumentNullException ("buildProvider");
406
407                         if (name == null || name == "")
408                                 throw new ArgumentNullException ("name");
409
410                         string filename = GetTempFilePhysicalPath ("resource");
411                         Stream stream = File.OpenWrite (filename);
412                         ResourceFiles [name] = filename;
413                         return stream;
414                 }
415
416                 [MonoTODO ("Not implemented, does nothing")]
417                 public void GenerateTypeFactory (string typeName)
418                 {
419                         // Do nothing by now.
420                 }
421
422                 public string GetTempFilePhysicalPath (string extension)
423                 {
424                         if (extension == null)
425                                 throw new ArgumentNullException ("extension");
426
427                         string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
428                         temp_files.AddFile (newFileName, KeepFiles);
429
430                         return newFileName;
431                 }
432
433                 public CodeDomProvider CodeDomProvider {
434                         get { return provider; }
435                 }
436
437                 List <Assembly> ReferencedAssemblies {
438                         get {
439                                 if (referenced_assemblies == null)
440                                         referenced_assemblies = new List <Assembly> ();
441
442                                 return referenced_assemblies;
443                         }
444                 }
445                 
446                 CodeUnit CheckForPartialTypes (CodeUnit codeUnit)
447                 {
448                         CodeTypeDeclarationCollection types;
449                         CompileUnitPartialType partialType;
450                         string partialTypeName;
451                         List <CompileUnitPartialType> tmp;
452                         Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
453                         
454                         foreach (CodeNamespace ns in codeUnit.Unit.Namespaces) {
455                                 if (ns == null)
456                                         continue;
457                                 types = ns.Types;
458                                 if (types == null || types.Count == 0)
459                                         continue;
460
461                                 foreach (CodeTypeDeclaration type in types) {
462                                         if (type == null)
463                                                 continue;
464
465                                         if (type.IsPartial) {
466                                                 partialType = new CompileUnitPartialType (codeUnit.Unit, ns, type);
467                                                 partialTypeName = partialType.TypeName;
468                                                 
469                                                 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
470                                                         tmp = new List <CompileUnitPartialType> (1);
471                                                         partialTypes.Add (partialTypeName, tmp);
472                                                 }
473                                                 tmp.Add (partialType);
474                                         }
475                                 }
476                         }
477                                                 
478                         return codeUnit;
479                 }
480                 
481                 void ProcessPartialTypes ()
482                 {
483                         Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
484                         if (partialTypes.Count == 0)
485                                 return;
486                         
487                         foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
488                                 ProcessType (kvp.Value);
489                 }
490
491                 void ProcessType (List <CompileUnitPartialType> typeList)
492                 {
493                         CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
494                         int counter = 0;
495                         
496                         foreach (CompileUnitPartialType type in typeList) {
497                                 if (counter == 0) {
498                                         types [0] = type;
499                                         counter++;
500                                         continue;
501                                 }
502
503                                 for (int i = 0; i < counter; i++)
504                                         CompareTypes (types [i], type);
505                                 types [counter++] = type;
506                         }
507                 }
508
509                 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
510                 {
511                         CodeTypeDeclaration sourceType = source.PartialType;
512                         CodeTypeMemberCollection targetMembers = target.PartialType.Members;
513                         List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
514                         
515                         foreach (CodeTypeMember member in targetMembers) {
516                                 if (TypeHasMember (sourceType, member))
517                                         membersToRemove.Add (member);
518                         }
519
520                         foreach (CodeTypeMember member in membersToRemove)
521                                 targetMembers.Remove (member);
522                 }
523
524                 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
525                 {
526                         if (type == null || member == null)
527                                 return false;
528
529                         return (FindMemberByName (type, member.Name) != null);
530                 }
531
532                 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
533                 {
534                         foreach (CodeTypeMember m in type.Members) {
535                                 if (m == null || m.Name != name)
536                                         continue;
537                                 return m;
538                         }
539
540                         return null;
541                 }
542
543                 internal CompilerResults BuildAssembly ()
544                 {
545                         return BuildAssembly (null, CompilerOptions);
546                 }
547                 
548                 internal CompilerResults BuildAssembly (VirtualPath virtualPath)
549                 {
550                         return BuildAssembly (virtualPath, CompilerOptions);
551                 }
552                 
553                 internal CompilerResults BuildAssembly (CompilerParameters options)
554                 {
555                         return BuildAssembly (null, options);
556                 }
557                 
558                 internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
559                 {
560                         if (options == null)
561                                 throw new ArgumentNullException ("options");
562
563                         options.TempFiles = temp_files;
564                         if (options.OutputAssembly == null)
565                                 options.OutputAssembly = OutputAssemblyName;
566
567                         ProcessPartialTypes ();
568                         
569                         CompilerResults results;
570                         CodeUnit [] units = GetUnitsAsArray ();
571
572                         // Since we may have some source files and some code
573                         // units, we generate code from all of them and then
574                         // compile the assembly from the set of temporary source
575                         // files. This also facilates possible debugging for the
576                         // end user, since they get the code beforehand.
577                         List <string> files = SourceFiles;
578                         Dictionary <string, string> resources = ResourceFiles;
579
580                         if (units.Length == 0 && files.Count == 0 && resources.Count == 0 && options.EmbeddedResources.Count == 0)
581                                 return null;
582                         
583                         string filename;
584                         StreamWriter sw = null;
585                         
586                         foreach (CodeUnit unit in units) {
587                                 filename = GetTempFilePhysicalPath (provider.FileExtension);
588                                 try {
589                                         sw = new StreamWriter (File.OpenWrite (filename), Encoding.UTF8);
590                                         provider.GenerateCodeFromCompileUnit (unit.Unit, sw, null);
591                                         files.Add (filename);
592                                 } catch {
593                                         throw;
594                                 } finally {
595                                         if (sw != null) {
596                                                 sw.Flush ();
597                                                 sw.Close ();
598                                         }
599                                 }
600
601                                 if (unit.BuildProvider != null)
602                                         AddPathToBuilderMap (filename, unit.BuildProvider);
603                         }
604
605                         foreach (KeyValuePair <string, string> de in resources)
606                                 options.EmbeddedResources.Add (de.Value);
607
608                         AddAssemblyReference (BuildManager.GetReferencedAssemblies ());
609                         foreach (Assembly refasm in ReferencedAssemblies) {
610                                 string path = new Uri (refasm.CodeBase).LocalPath;
611                                 options.ReferencedAssemblies.Add (path);
612                         }
613                         
614                         results = provider.CompileAssemblyFromFile (options, files.ToArray ());
615
616                         if (results.NativeCompilerReturnValue != 0) {
617                                 string fileText = null;
618                                 try {
619                                         using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
620                                                 fileText = sr.ReadToEnd ();
621                                         }
622                                 } catch (Exception) {}
623                                 
624 #if DEBUG
625                                 Console.WriteLine ("********************************************************************");
626                                 Console.WriteLine ("Compilation failed.");
627                                 Console.WriteLine ("Output:");
628                                 foreach (string s in results.Output)
629                                         Console.WriteLine ("  " + s);
630                                 Console.WriteLine ("\nErrors:");
631                                 foreach (CompilerError err in results.Errors)
632                                         Console.WriteLine (err);
633                                 Console.WriteLine ("File name: {0}", results.Errors [0].FileName);
634                                 Console.WriteLine ("File text:\n{0}\n", fileText);
635                                 Console.WriteLine ("********************************************************************");
636 #endif
637                                 
638                                 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results, fileText);
639                         }
640                         
641                         Assembly assembly = results.CompiledAssembly;
642                         if (assembly == null) {
643                                 if (!File.Exists (options.OutputAssembly)) {
644                                         results.TempFiles.Delete ();
645                                         throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results.Errors,
646                                                 "No assembly returned after compilation!?");
647                                 }
648
649                                 try {
650                                         results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
651                                 } catch (Exception ex) {
652                                         results.TempFiles.Delete ();
653                                         throw new HttpException ("Unable to load compiled assembly", ex);
654                                 }
655                         }
656
657                         if (!KeepFiles)
658                                 results.TempFiles.Delete ();
659                         return results;
660                 }
661         }
662 }
663 #endif
664