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.Generic;
39 using System.Collections.Specialized;
41 using System.Reflection;
42 using System.Web.Configuration;
43 using System.Web.Util;
45 namespace System.Web.Compilation {
46 internal class CompileUnitPartialType
48 public readonly CodeCompileUnit Unit;
49 public readonly CodeNamespace ParentNamespace;
50 public readonly CodeTypeDeclaration PartialType;
54 public string TypeName {
56 if (typeName == null) {
57 if (ParentNamespace == null || PartialType == null)
60 typeName = ParentNamespace.Name;
61 if (String.IsNullOrEmpty (typeName))
62 typeName = PartialType.Name;
64 typeName += "." + PartialType.Name;
71 public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
74 this.ParentNamespace = parentNamespace;
75 this.PartialType = type;
79 public class AssemblyBuilder {
80 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
82 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
84 CodeDomProvider provider;
85 CompilerParameters parameters;
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;
98 internal AssemblyBuilder (CodeDomProvider provider)
99 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
102 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
103 : this (null, provider, assemblyBaseName)
106 internal AssemblyBuilder (string virtualPath, CodeDomProvider provider)
107 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
110 internal AssemblyBuilder (string virtualPath, CodeDomProvider provider, string assemblyBaseName)
112 this.provider = provider;
113 this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
115 units = new List <CodeCompileUnit> ();
117 CompilationSection section;
118 if (virtualPath != null)
119 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation", virtualPath);
121 section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
122 string tempdir = section.TempDirectory;
123 if (String.IsNullOrEmpty (tempdir))
124 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
127 KeepFiles = section.Debug;
129 temp_files = new TempFileCollection (tempdir, KeepFiles);
132 internal string OutputFilesPrefix {
134 if (outputFilesPrefix == null)
135 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
137 return outputFilesPrefix;
141 if (String.IsNullOrEmpty (value))
142 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
144 outputFilesPrefix = value;
145 outputAssemblyPrefix = null;
146 outputAssemblyName = null;
150 internal string OutputAssemblyPrefix {
152 if (outputAssemblyPrefix == null) {
153 string basePath = temp_files.BasePath;
154 string baseName = Path.GetFileName (basePath);
155 string baseDir = Path.GetDirectoryName (basePath);
157 outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
160 return outputAssemblyPrefix;
164 internal string OutputAssemblyName {
166 if (outputAssemblyName == null)
167 outputAssemblyName = OutputAssemblyPrefix + ".dll";
169 return outputAssemblyName;
173 internal TempFileCollection TempFiles {
174 get { return temp_files; }
177 internal CompilerParameters CompilerOptions {
178 get { return parameters; }
179 set { parameters = value; }
182 internal CodeCompileUnit [] GetUnitsAsArray ()
184 CodeCompileUnit [] result = new CodeCompileUnit [units.Count];
185 units.CopyTo (result, 0);
189 internal List <CodeCompileUnit> Units {
192 units = new List <CodeCompileUnit> ();
197 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
199 if (partial_types == null)
200 partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
201 return partial_types;
205 Dictionary <string, bool> CodeFiles {
207 if (code_files == null)
208 code_files = new Dictionary <string, bool> ();
213 List <string> SourceFiles {
215 if (source_files == null)
216 source_files = new List <string> ();
221 Dictionary <string, string> ResourceFiles {
223 if (resource_files == null)
224 resource_files = new Dictionary <string, string> ();
225 return resource_files;
229 public void AddAssemblyReference (Assembly a)
232 throw new ArgumentNullException ("a");
234 List <Assembly> assemblies = ReferencedAssemblies;
236 if (assemblies.Contains (a))
242 internal void AddAssemblyReference (List <Assembly> asmlist)
247 foreach (Assembly a in asmlist) {
251 AddAssemblyReference (a);
255 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
257 if (compileUnit == null)
258 throw new ArgumentNullException ("compileUnit");
259 units.Add (CheckForPartialTypes (compileUnit));
262 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
264 if (buildProvider == null)
265 throw new ArgumentNullException ("buildProvider");
267 if (compileUnit == null)
268 throw new ArgumentNullException ("compileUnit");
270 units.Add (CheckForPartialTypes (compileUnit));
273 public TextWriter CreateCodeFile (BuildProvider buildProvider)
275 if (buildProvider == null)
276 throw new ArgumentNullException ("buildProvider");
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);
284 internal void AddCodeFile (string path)
286 AddCodeFile (path, null);
289 internal void AddCodeFile (string path, BuildProvider bp)
291 if (String.IsNullOrEmpty (path))
294 Dictionary <string, bool> codeFiles = CodeFiles;
295 if (codeFiles.ContainsKey (path))
298 codeFiles.Add (path, true);
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);
309 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
311 if (buildProvider == null)
312 throw new ArgumentNullException ("buildProvider");
314 if (name == null || name == "")
315 throw new ArgumentNullException ("name");
317 string filename = GetTempFilePhysicalPath ("resource");
318 Stream stream = File.OpenWrite (filename);
319 ResourceFiles [name] = filename;
323 [MonoTODO ("Not implemented, does nothing")]
324 public void GenerateTypeFactory (string typeName)
326 // Do nothing by now.
329 public string GetTempFilePhysicalPath (string extension)
331 if (extension == null)
332 throw new ArgumentNullException ("extension");
334 string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
335 temp_files.AddFile (newFileName, KeepFiles);
340 public CodeDomProvider CodeDomProvider {
341 get { return provider; }
344 List <Assembly> ReferencedAssemblies {
346 if (referenced_assemblies == null)
347 referenced_assemblies = new List <Assembly> ();
349 return referenced_assemblies;
353 CodeCompileUnit CheckForPartialTypes (CodeCompileUnit compileUnit)
355 if (compileUnit == null)
358 CodeTypeDeclarationCollection types;
359 CompileUnitPartialType partialType;
360 string partialTypeName;
361 List <CompileUnitPartialType> tmp;
362 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
364 foreach (CodeNamespace ns in compileUnit.Namespaces) {
368 if (types == null || types.Count == 0)
371 foreach (CodeTypeDeclaration type in types) {
375 if (type.IsPartial) {
376 partialType = new CompileUnitPartialType (compileUnit, ns, type);
377 partialTypeName = partialType.TypeName;
379 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
380 tmp = new List <CompileUnitPartialType> (1);
381 partialTypes.Add (partialTypeName, tmp);
383 tmp.Add (partialType);
391 void ProcessPartialTypes ()
393 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
394 if (partialTypes.Count == 0)
397 foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
398 ProcessType (kvp.Value);
401 void ProcessType (List <CompileUnitPartialType> typeList)
403 CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
406 foreach (CompileUnitPartialType type in typeList) {
413 for (int i = 0; i < counter; i++)
414 CompareTypes (types [i], type);
415 types [counter++] = type;
419 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
421 CodeTypeDeclaration sourceType = source.PartialType;
422 CodeTypeMemberCollection targetMembers = target.PartialType.Members;
423 List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
425 foreach (CodeTypeMember member in targetMembers) {
426 if (TypeHasMember (sourceType, member))
427 membersToRemove.Add (member);
430 foreach (CodeTypeMember member in membersToRemove)
431 targetMembers.Remove (member);
434 bool TypeHasMember (CodeTypeDeclaration type, CodeMemberMethod member)
436 if (type == null || member == null)
439 CodeMemberMethod method = FindMemberByName (type, member.Name) as CodeMemberMethod;
443 if (method.Parameters.Count != member.Parameters.Count)
449 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
451 if (type == null || member == null)
454 return (FindMemberByName (type, member.Name) != null);
457 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
459 foreach (CodeTypeMember m in type.Members) {
460 if (m == null || m.Name != name)
468 internal CompilerResults BuildAssembly (string virtualPath)
470 return BuildAssembly (virtualPath, CompilerOptions);
473 internal CompilerResults BuildAssembly (CompilerParameters options)
475 return BuildAssembly (null, options);
478 internal CompilerResults BuildAssembly (string virtualPath, CompilerParameters options)
481 throw new ArgumentNullException ("options");
483 options.TempFiles = temp_files;
484 if (options.OutputAssembly == null)
485 options.OutputAssembly = OutputAssemblyName;
487 ProcessPartialTypes ();
489 CompilerResults results;
490 CodeCompileUnit [] units = GetUnitsAsArray ();
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;
499 StreamWriter sw = null;
500 foreach (CodeCompileUnit unit in units) {
501 filename = GetTempFilePhysicalPath (provider.FileExtension);
503 sw = new StreamWriter (File.OpenWrite (filename), WebEncoding.FileEncoding);
504 provider.GenerateCodeFromCompileUnit (unit, sw, null);
505 files.Add (filename);
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);
521 results = provider.CompileAssemblyFromFile (options, files.ToArray ());
523 if (results.NativeCompilerReturnValue != 0) {
524 string fileText = null;
526 using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
527 fileText = sr.ReadToEnd ();
529 } catch (Exception) {}
531 throw new CompilationException (virtualPath, results.Errors, fileText);
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!?");
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);
551 results.TempFiles.Delete ();