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 internal 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 {
83 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
84 const int COPY_BUFFER_SIZE = 8192;
86 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
88 CodeDomProvider provider;
89 CompilerParameters parameters;
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;
102 internal AssemblyBuilder (CodeDomProvider provider)
103 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
106 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
107 : this (null, provider, assemblyBaseName)
110 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
111 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
114 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
116 this.provider = provider;
117 this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
119 units = new List <CodeCompileUnit> ();
121 CompilationSection section;
122 if (virtualPath != null)
123 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Absolute);
125 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
126 string tempdir = section.TempDirectory;
127 if (String.IsNullOrEmpty (tempdir))
128 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
131 KeepFiles = section.Debug;
133 temp_files = new TempFileCollection (tempdir, KeepFiles);
136 internal string OutputFilesPrefix {
138 if (outputFilesPrefix == null)
139 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
141 return outputFilesPrefix;
145 if (String.IsNullOrEmpty (value))
146 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
148 outputFilesPrefix = value;
149 outputAssemblyPrefix = null;
150 outputAssemblyName = null;
154 internal string OutputAssemblyPrefix {
156 if (outputAssemblyPrefix == null) {
157 string basePath = temp_files.BasePath;
158 string baseName = Path.GetFileName (basePath);
159 string baseDir = Path.GetDirectoryName (basePath);
161 outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
164 return outputAssemblyPrefix;
168 internal string OutputAssemblyName {
170 if (outputAssemblyName == null)
171 outputAssemblyName = OutputAssemblyPrefix + ".dll";
173 return outputAssemblyName;
177 internal TempFileCollection TempFiles {
178 get { return temp_files; }
181 internal CompilerParameters CompilerOptions {
182 get { return parameters; }
183 set { parameters = value; }
186 internal CodeCompileUnit [] GetUnitsAsArray ()
188 CodeCompileUnit [] result = new CodeCompileUnit [units.Count];
189 units.CopyTo (result, 0);
193 internal List <CodeCompileUnit> Units {
196 units = new List <CodeCompileUnit> ();
201 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
203 if (partial_types == null)
204 partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
205 return partial_types;
209 Dictionary <string, bool> CodeFiles {
211 if (code_files == null)
212 code_files = new Dictionary <string, bool> ();
217 List <string> SourceFiles {
219 if (source_files == null)
220 source_files = new List <string> ();
225 Dictionary <string, string> ResourceFiles {
227 if (resource_files == null)
228 resource_files = new Dictionary <string, string> ();
229 return resource_files;
233 public void AddAssemblyReference (Assembly a)
236 throw new ArgumentNullException ("a");
238 List <Assembly> assemblies = ReferencedAssemblies;
240 if (assemblies.Contains (a))
246 internal void AddAssemblyReference (string assemblyLocation)
249 Assembly asm = Assembly.LoadFrom (assemblyLocation);
253 AddAssemblyReference (asm);
255 // ignore, it will come up later
259 internal void AddAssemblyReference (ICollection asmcoll)
261 if (asmcoll == null || asmcoll.Count == 0)
265 foreach (object o in asmcoll) {
270 AddAssemblyReference (asm);
274 internal void AddAssemblyReference (List <Assembly> asmlist)
279 foreach (Assembly a in asmlist) {
283 AddAssemblyReference (a);
287 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
289 if (compileUnit == null)
290 throw new ArgumentNullException ("compileUnit");
291 units.Add (CheckForPartialTypes (compileUnit));
294 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
296 if (buildProvider == null)
297 throw new ArgumentNullException ("buildProvider");
299 if (compileUnit == null)
300 throw new ArgumentNullException ("compileUnit");
302 units.Add (CheckForPartialTypes (compileUnit));
305 public TextWriter CreateCodeFile (BuildProvider buildProvider)
307 if (buildProvider == null)
308 throw new ArgumentNullException ("buildProvider");
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));
316 internal void AddCodeFile (string path)
318 AddCodeFile (path, null, false);
321 internal void AddCodeFile (string path, BuildProvider bp)
323 AddCodeFile (path, bp, false);
326 internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
328 if (String.IsNullOrEmpty (path))
331 Dictionary <string, bool> codeFiles = CodeFiles;
332 if (codeFiles.ContainsKey (path))
335 codeFiles.Add (path, true);
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);
344 VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
346 throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
348 CopyFile (vf.Open (), filename);
350 CopyFile (path, filename);
352 SourceFiles.Add (filename);
355 void CopyFile (string input, string filename)
357 CopyFile (new FileStream (input, FileMode.Open, FileAccess.Read), filename);
360 void CopyFile (Stream input, string filename)
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 ());
369 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
371 if (buildProvider == null)
372 throw new ArgumentNullException ("buildProvider");
374 if (name == null || name == "")
375 throw new ArgumentNullException ("name");
377 string filename = GetTempFilePhysicalPath ("resource");
378 Stream stream = File.OpenWrite (filename);
379 ResourceFiles [name] = filename;
383 [MonoTODO ("Not implemented, does nothing")]
384 public void GenerateTypeFactory (string typeName)
386 // Do nothing by now.
389 public string GetTempFilePhysicalPath (string extension)
391 if (extension == null)
392 throw new ArgumentNullException ("extension");
394 string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
395 temp_files.AddFile (newFileName, KeepFiles);
400 public CodeDomProvider CodeDomProvider {
401 get { return provider; }
404 List <Assembly> ReferencedAssemblies {
406 if (referenced_assemblies == null)
407 referenced_assemblies = new List <Assembly> ();
409 return referenced_assemblies;
413 CodeCompileUnit CheckForPartialTypes (CodeCompileUnit compileUnit)
415 if (compileUnit == null)
418 CodeTypeDeclarationCollection types;
419 CompileUnitPartialType partialType;
420 string partialTypeName;
421 List <CompileUnitPartialType> tmp;
422 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
424 foreach (CodeNamespace ns in compileUnit.Namespaces) {
428 if (types == null || types.Count == 0)
431 foreach (CodeTypeDeclaration type in types) {
435 if (type.IsPartial) {
436 partialType = new CompileUnitPartialType (compileUnit, ns, type);
437 partialTypeName = partialType.TypeName;
439 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
440 tmp = new List <CompileUnitPartialType> (1);
441 partialTypes.Add (partialTypeName, tmp);
443 tmp.Add (partialType);
451 void ProcessPartialTypes ()
453 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
454 if (partialTypes.Count == 0)
457 foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
458 ProcessType (kvp.Value);
461 void ProcessType (List <CompileUnitPartialType> typeList)
463 CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
466 foreach (CompileUnitPartialType type in typeList) {
473 for (int i = 0; i < counter; i++)
474 CompareTypes (types [i], type);
475 types [counter++] = type;
479 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
481 CodeTypeDeclaration sourceType = source.PartialType;
482 CodeTypeMemberCollection targetMembers = target.PartialType.Members;
483 List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
485 foreach (CodeTypeMember member in targetMembers) {
486 if (TypeHasMember (sourceType, member))
487 membersToRemove.Add (member);
490 foreach (CodeTypeMember member in membersToRemove)
491 targetMembers.Remove (member);
494 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
496 if (type == null || member == null)
499 return (FindMemberByName (type, member.Name) != null);
502 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
504 foreach (CodeTypeMember m in type.Members) {
505 if (m == null || m.Name != name)
513 internal CompilerResults BuildAssembly ()
515 return BuildAssembly (null, CompilerOptions);
518 internal CompilerResults BuildAssembly (VirtualPath virtualPath)
520 return BuildAssembly (virtualPath, CompilerOptions);
523 internal CompilerResults BuildAssembly (CompilerParameters options)
525 return BuildAssembly (null, options);
528 internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
531 throw new ArgumentNullException ("options");
533 options.TempFiles = temp_files;
534 if (options.OutputAssembly == null)
535 options.OutputAssembly = OutputAssemblyName;
537 ProcessPartialTypes ();
539 CompilerResults results;
540 CodeCompileUnit [] units = GetUnitsAsArray ();
542 // Since we may have some source files and some code
543 // units, we generate code from all of them and then
544 // compile the assembly from the set of temporary source
545 // files. This also facilates possible debugging for the
546 // end user, since they get the code beforehand.
547 List <string> files = SourceFiles;
548 Dictionary <string, string> resources = ResourceFiles;
550 if (units.Length == 0 && files.Count == 0 && resources.Count == 0 && options.EmbeddedResources.Count == 0)
554 StreamWriter sw = null;
556 foreach (CodeCompileUnit unit in units) {
557 filename = GetTempFilePhysicalPath (provider.FileExtension);
559 sw = new StreamWriter (File.OpenWrite (filename), Encoding.UTF8);
560 provider.GenerateCodeFromCompileUnit (unit, sw, null);
561 files.Add (filename);
572 foreach (KeyValuePair <string, string> de in resources)
573 options.EmbeddedResources.Add (de.Value);
574 AddAssemblyReference (BuildManager.GetReferencedAssemblies ());
575 foreach (Assembly refasm in ReferencedAssemblies)
576 options.ReferencedAssemblies.Add (refasm.Location);
578 results = provider.CompileAssemblyFromFile (options, files.ToArray ());
580 if (results.NativeCompilerReturnValue != 0) {
581 string fileText = null;
583 using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
584 fileText = sr.ReadToEnd ();
586 } catch (Exception) {}
589 Console.WriteLine ("********************************************************************");
590 Console.WriteLine ("Compilation failed.");
591 Console.WriteLine ("Output:");
592 foreach (string s in results.Output)
593 Console.WriteLine (" " + s);
594 Console.WriteLine ("\nErrors:");
595 foreach (CompilerError err in results.Errors)
596 Console.WriteLine (err);
597 Console.WriteLine ("File name: {0}", results.Errors [0].FileName);
598 Console.WriteLine ("File text:\n{0}\n", fileText);
599 Console.WriteLine ("********************************************************************");
602 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results, fileText);
605 Assembly assembly = results.CompiledAssembly;
606 if (assembly == null) {
607 if (!File.Exists (options.OutputAssembly)) {
608 results.TempFiles.Delete ();
609 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results.Errors,
610 "No assembly returned after compilation!?");
614 results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
615 } catch (Exception ex) {
616 results.TempFiles.Delete ();
617 throw new HttpException ("Unable to load compiled assembly", ex);
622 results.TempFiles.Delete ();