2 // System.Web.Compilation.AssemblyBuilder
5 // Chris Toshok (toshok@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // Marek Habersack (mhabersack@novell.com)
9 // (C) 2006-2008 Novell, Inc (http://www.novell.com)
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
37 using System.CodeDom.Compiler;
38 using System.Collections;
39 using System.Collections.Generic;
40 using System.Collections.Specialized;
42 using System.Reflection;
44 using System.Web.Configuration;
45 using System.Web.Util;
46 using System.Web.Hosting;
48 namespace System.Web.Compilation {
49 class CompileUnitPartialType
51 public readonly CodeCompileUnit Unit;
52 public readonly CodeNamespace ParentNamespace;
53 public readonly CodeTypeDeclaration PartialType;
57 public string TypeName {
59 if (typeName == null) {
60 if (ParentNamespace == null || PartialType == null)
63 typeName = ParentNamespace.Name;
64 if (String.IsNullOrEmpty (typeName))
65 typeName = PartialType.Name;
67 typeName += "." + PartialType.Name;
74 public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
77 this.ParentNamespace = parentNamespace;
78 this.PartialType = type;
82 public class AssemblyBuilder
86 public readonly BuildProvider BuildProvider;
87 public readonly CodeCompileUnit Unit;
89 public CodeUnit (BuildProvider bp, CodeCompileUnit unit)
91 this.BuildProvider = bp;
96 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
97 const int COPY_BUFFER_SIZE = 8192;
99 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
101 CodeDomProvider provider;
102 CompilerParameters parameters;
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;
116 internal AssemblyBuilder (CodeDomProvider provider)
117 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
120 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
121 : this (null, provider, assemblyBaseName)
124 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
125 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
128 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
130 this.provider = provider;
131 this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
133 units = new List <CodeUnit> ();
135 CompilationSection section;
136 if (virtualPath != null)
137 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Absolute);
139 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
140 string tempdir = section.TempDirectory;
141 if (String.IsNullOrEmpty (tempdir))
142 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
145 KeepFiles = section.Debug;
147 temp_files = new TempFileCollection (tempdir, KeepFiles);
150 internal string OutputFilesPrefix {
152 if (outputFilesPrefix == null)
153 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
155 return outputFilesPrefix;
159 if (String.IsNullOrEmpty (value))
160 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
162 outputFilesPrefix = value;
163 outputAssemblyPrefix = null;
164 outputAssemblyName = null;
168 internal string OutputAssemblyPrefix {
170 if (outputAssemblyPrefix == null) {
171 string basePath = temp_files.BasePath;
172 string baseName = Path.GetFileName (basePath);
173 string baseDir = Path.GetDirectoryName (basePath);
175 outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
178 return outputAssemblyPrefix;
182 internal string OutputAssemblyName {
184 if (outputAssemblyName == null)
185 outputAssemblyName = OutputAssemblyPrefix + ".dll";
187 return outputAssemblyName;
191 internal TempFileCollection TempFiles {
192 get { return temp_files; }
195 internal CompilerParameters CompilerOptions {
196 get { return parameters; }
197 set { parameters = value; }
200 CodeUnit[] GetUnitsAsArray ()
202 CodeUnit[] result = new CodeUnit [units.Count];
203 units.CopyTo (result, 0);
207 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
209 if (partial_types == null)
210 partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
211 return partial_types;
215 Dictionary <string, bool> CodeFiles {
217 if (code_files == null)
218 code_files = new Dictionary <string, bool> ();
223 List <string> SourceFiles {
225 if (source_files == null)
226 source_files = new List <string> ();
231 Dictionary <string, string> ResourceFiles {
233 if (resource_files == null)
234 resource_files = new Dictionary <string, string> ();
235 return resource_files;
239 internal BuildProvider GetBuildProviderForPhysicalFilePath (string path)
241 if (String.IsNullOrEmpty (path) || path_to_buildprovider == null || path_to_buildprovider.Count == 0)
245 if (path_to_buildprovider.TryGetValue (path, out ret))
251 public void AddAssemblyReference (Assembly a)
254 throw new ArgumentNullException ("a");
256 List <Assembly> assemblies = ReferencedAssemblies;
258 if (assemblies.Contains (a))
264 internal void AddAssemblyReference (string assemblyLocation)
267 Assembly asm = Assembly.LoadFrom (assemblyLocation);
271 AddAssemblyReference (asm);
273 // ignore, it will come up later
277 internal void AddAssemblyReference (ICollection asmcoll)
279 if (asmcoll == null || asmcoll.Count == 0)
283 foreach (object o in asmcoll) {
288 AddAssemblyReference (asm);
292 internal void AddAssemblyReference (List <Assembly> asmlist)
297 foreach (Assembly a in asmlist) {
301 AddAssemblyReference (a);
305 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
307 if (compileUnit == null)
308 throw new ArgumentNullException ("compileUnit");
309 units.Add (CheckForPartialTypes (new CodeUnit (null, compileUnit)));
312 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
314 if (buildProvider == null)
315 throw new ArgumentNullException ("buildProvider");
317 if (compileUnit == null)
318 throw new ArgumentNullException ("compileUnit");
320 units.Add (CheckForPartialTypes (new CodeUnit (buildProvider, compileUnit)));
323 void AddPathToBuilderMap (string path, BuildProvider bp)
325 if (path_to_buildprovider == null)
326 path_to_buildprovider = new Dictionary <string, BuildProvider> ();
328 if (path_to_buildprovider.ContainsKey (path))
331 path_to_buildprovider.Add (path, bp);
334 public TextWriter CreateCodeFile (BuildProvider buildProvider)
336 if (buildProvider == null)
337 throw new ArgumentNullException ("buildProvider");
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));
346 internal void AddCodeFile (string path)
348 AddCodeFile (path, null, false);
351 internal void AddCodeFile (string path, BuildProvider bp)
353 AddCodeFile (path, bp, false);
356 internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
358 if (String.IsNullOrEmpty (path))
361 Dictionary <string, bool> codeFiles = CodeFiles;
362 if (codeFiles.ContainsKey (path))
365 codeFiles.Add (path, true);
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);
374 VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
376 throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
378 CopyFile (vf.Open (), filename);
380 CopyFile (path, filename);
383 AddPathToBuilderMap (filename, bp);
385 SourceFiles.Add (filename);
388 void CopyFile (string input, string filename)
390 CopyFile (new FileStream (input, FileMode.Open, FileAccess.Read), filename);
393 void CopyFile (Stream input, string filename)
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 ());
402 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
404 if (buildProvider == null)
405 throw new ArgumentNullException ("buildProvider");
407 if (name == null || name == "")
408 throw new ArgumentNullException ("name");
410 string filename = GetTempFilePhysicalPath ("resource");
411 Stream stream = File.OpenWrite (filename);
412 ResourceFiles [name] = filename;
416 [MonoTODO ("Not implemented, does nothing")]
417 public void GenerateTypeFactory (string typeName)
419 // Do nothing by now.
422 public string GetTempFilePhysicalPath (string extension)
424 if (extension == null)
425 throw new ArgumentNullException ("extension");
427 string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
428 temp_files.AddFile (newFileName, KeepFiles);
433 public CodeDomProvider CodeDomProvider {
434 get { return provider; }
437 List <Assembly> ReferencedAssemblies {
439 if (referenced_assemblies == null)
440 referenced_assemblies = new List <Assembly> ();
442 return referenced_assemblies;
446 CodeUnit CheckForPartialTypes (CodeUnit codeUnit)
448 CodeTypeDeclarationCollection types;
449 CompileUnitPartialType partialType;
450 string partialTypeName;
451 List <CompileUnitPartialType> tmp;
452 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
454 foreach (CodeNamespace ns in codeUnit.Unit.Namespaces) {
458 if (types == null || types.Count == 0)
461 foreach (CodeTypeDeclaration type in types) {
465 if (type.IsPartial) {
466 partialType = new CompileUnitPartialType (codeUnit.Unit, ns, type);
467 partialTypeName = partialType.TypeName;
469 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
470 tmp = new List <CompileUnitPartialType> (1);
471 partialTypes.Add (partialTypeName, tmp);
473 tmp.Add (partialType);
481 void ProcessPartialTypes ()
483 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
484 if (partialTypes.Count == 0)
487 foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
488 ProcessType (kvp.Value);
491 void ProcessType (List <CompileUnitPartialType> typeList)
493 CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
496 foreach (CompileUnitPartialType type in typeList) {
503 for (int i = 0; i < counter; i++)
504 CompareTypes (types [i], type);
505 types [counter++] = type;
509 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
511 CodeTypeDeclaration sourceType = source.PartialType;
512 CodeTypeMemberCollection targetMembers = target.PartialType.Members;
513 List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
515 foreach (CodeTypeMember member in targetMembers) {
516 if (TypeHasMember (sourceType, member))
517 membersToRemove.Add (member);
520 foreach (CodeTypeMember member in membersToRemove)
521 targetMembers.Remove (member);
524 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
526 if (type == null || member == null)
529 return (FindMemberByName (type, member.Name) != null);
532 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
534 foreach (CodeTypeMember m in type.Members) {
535 if (m == null || m.Name != name)
543 internal CompilerResults BuildAssembly ()
545 return BuildAssembly (null, CompilerOptions);
548 internal CompilerResults BuildAssembly (VirtualPath virtualPath)
550 return BuildAssembly (virtualPath, CompilerOptions);
553 internal CompilerResults BuildAssembly (CompilerParameters options)
555 return BuildAssembly (null, options);
558 internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
561 throw new ArgumentNullException ("options");
563 options.TempFiles = temp_files;
564 if (options.OutputAssembly == null)
565 options.OutputAssembly = OutputAssemblyName;
567 ProcessPartialTypes ();
569 CompilerResults results;
570 CodeUnit [] units = GetUnitsAsArray ();
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;
580 if (units.Length == 0 && files.Count == 0 && resources.Count == 0 && options.EmbeddedResources.Count == 0)
584 StreamWriter sw = null;
586 foreach (CodeUnit unit in units) {
587 filename = GetTempFilePhysicalPath (provider.FileExtension);
589 sw = new StreamWriter (File.OpenWrite (filename), Encoding.UTF8);
590 provider.GenerateCodeFromCompileUnit (unit.Unit, sw, null);
591 files.Add (filename);
601 if (unit.BuildProvider != null)
602 AddPathToBuilderMap (filename, unit.BuildProvider);
605 foreach (KeyValuePair <string, string> de in resources)
606 options.EmbeddedResources.Add (de.Value);
608 AddAssemblyReference (BuildManager.GetReferencedAssemblies ());
609 foreach (Assembly refasm in ReferencedAssemblies) {
610 string path = new Uri (refasm.CodeBase).LocalPath;
611 options.ReferencedAssemblies.Add (path);
614 results = provider.CompileAssemblyFromFile (options, files.ToArray ());
616 if (results.NativeCompilerReturnValue != 0) {
617 string fileText = null;
619 using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
620 fileText = sr.ReadToEnd ();
622 } catch (Exception) {}
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 ("********************************************************************");
638 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results, fileText);
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!?");
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);
658 results.TempFiles.Delete ();