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.Security.Cryptography;
43 using System.Reflection;
45 using System.Web.Configuration;
46 using System.Web.Util;
47 using System.Web.Hosting;
49 namespace System.Web.Compilation
51 class CompileUnitPartialType
53 public readonly CodeCompileUnit Unit;
54 public readonly CodeNamespace ParentNamespace;
55 public readonly CodeTypeDeclaration PartialType;
59 public string TypeName {
61 if (typeName == null) {
62 if (ParentNamespace == null || PartialType == null)
65 typeName = ParentNamespace.Name;
66 if (String.IsNullOrEmpty (typeName))
67 typeName = PartialType.Name;
69 typeName += "." + PartialType.Name;
76 public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
79 this.ParentNamespace = parentNamespace;
80 this.PartialType = type;
84 public class AssemblyBuilder
88 public readonly BuildProvider BuildProvider;
89 public readonly CodeCompileUnit Unit;
91 public CodeUnit (BuildProvider bp, CodeCompileUnit unit)
93 this.BuildProvider = bp;
98 interface ICodePragmaGenerator
100 int ReserveSpace (string filename);
101 void DecorateFile (string path, string filename, MD5 checksum, Encoding enc);
104 class CSharpCodePragmaGenerator : ICodePragmaGenerator
106 // Copied from CSharpCodeGenerator.cs
107 string QuoteSnippetString (string value)
109 // FIXME: this is weird, but works.
110 string output = value.Replace ("\\", "\\\\");
111 output = output.Replace ("\"", "\\\"");
112 output = output.Replace ("\t", "\\t");
113 output = output.Replace ("\r", "\\r");
114 output = output.Replace ("\n", "\\n");
116 return "\"" + output + "\"";
119 string ChecksumToHex (MD5 checksum)
121 var ret = new StringBuilder ();
122 foreach (byte b in checksum.Hash)
123 ret.Append (b.ToString ("X2"));
125 return ret.ToString ();
128 const int pragmaChecksumStaticCount = 23;
129 const int pragmaLineStaticCount = 8;
130 const int md5ChecksumCount = 32;
132 public int ReserveSpace (string filename)
134 return pragmaChecksumStaticCount +
135 pragmaLineStaticCount +
137 (QuoteSnippetString (filename).Length * 2) +
138 (Environment.NewLine.Length * 3) +
139 BaseCompiler.HashMD5.ToString ("B").Length;
142 public void DecorateFile (string path, string filename, MD5 checksum, Encoding enc)
144 string newline = Environment.NewLine;
145 var sb = new StringBuilder ();
147 sb.AppendFormat ("#pragma checksum {0} \"{1}\" \"{2}\"{3}{3}",
148 QuoteSnippetString (filename),
149 BaseCompiler.HashMD5.ToString ("B"),
150 ChecksumToHex (checksum),
152 sb.AppendFormat ("#line 1 {0}{1}", QuoteSnippetString (filename), newline);
154 byte[] bytes = enc.GetBytes (sb.ToString ());
155 using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Write)) {
156 fs.Seek (enc.GetPreamble ().Length, SeekOrigin.Begin);
157 fs.Write (bytes, 0, bytes.Length);
161 sb.AppendFormat ("{0}#line default{0}#line hidden{0}", newline);
162 bytes = Encoding.UTF8.GetBytes (sb.ToString ());
164 fs.Seek (0, SeekOrigin.End);
165 fs.Write (bytes, 0, bytes.Length);
173 class VBCodePragmaGenerator : ICodePragmaGenerator
175 const int pragmaExternalSourceCount = 21;
176 public int ReserveSpace (string filename)
178 return pragmaExternalSourceCount +
180 (Environment.NewLine.Length);
183 public void DecorateFile (string path, string filename, MD5 checksum, Encoding enc)
185 string newline = Environment.NewLine;
186 var sb = new StringBuilder ();
188 sb.AppendFormat ("#ExternalSource(\"{0}\",1){1}", filename, newline);
189 byte[] bytes = enc.GetBytes (sb.ToString ());
190 using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Write)) {
191 fs.Seek (enc.GetPreamble ().Length, SeekOrigin.Begin);
192 fs.Write (bytes, 0, bytes.Length);
196 sb.AppendFormat ("{0}#End ExternalSource{0}", newline);
197 bytes = enc.GetBytes (sb.ToString ());
198 fs.Seek (0, SeekOrigin.End);
199 fs.Write (bytes, 0, bytes.Length);
206 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
207 const int COPY_BUFFER_SIZE = 8192;
209 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
211 CodeDomProvider provider;
212 CompilerParameters parameters;
214 Dictionary <string, bool> code_files;
215 Dictionary <string, List <CompileUnitPartialType>> partial_types;
216 Dictionary <string, BuildProvider> path_to_buildprovider;
217 List <CodeUnit> units;
218 List <string> source_files;
219 List <Assembly> referenced_assemblies;
220 Dictionary <string, string> resource_files;
221 TempFileCollection temp_files;
222 string outputFilesPrefix;
223 string outputAssemblyPrefix;
224 string outputAssemblyName;
226 internal AssemblyBuilder (CodeDomProvider provider)
227 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
230 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
231 : this (null, provider, assemblyBaseName)
234 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
235 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
238 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
240 this.provider = provider;
241 this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
243 units = new List <CodeUnit> ();
245 CompilationSection section;
246 section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
247 string tempdir = section.TempDirectory;
248 if (String.IsNullOrEmpty (tempdir))
249 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
252 KeepFiles = section.Debug;
254 temp_files = new TempFileCollection (tempdir, KeepFiles);
257 internal string OutputFilesPrefix {
259 if (outputFilesPrefix == null)
260 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
262 return outputFilesPrefix;
266 if (String.IsNullOrEmpty (value))
267 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
269 outputFilesPrefix = value;
270 outputAssemblyPrefix = null;
271 outputAssemblyName = null;
275 internal string OutputAssemblyPrefix {
277 if (outputAssemblyPrefix == null) {
278 string basePath = temp_files.BasePath;
279 string baseName = Path.GetFileName (basePath);
280 string baseDir = Path.GetDirectoryName (basePath);
282 outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
285 return outputAssemblyPrefix;
289 internal string OutputAssemblyName {
291 if (outputAssemblyName == null)
292 outputAssemblyName = OutputAssemblyPrefix + ".dll";
294 return outputAssemblyName;
298 internal TempFileCollection TempFiles {
299 get { return temp_files; }
302 internal CompilerParameters CompilerOptions {
303 get { return parameters; }
304 set { parameters = value; }
307 CodeUnit[] GetUnitsAsArray ()
309 CodeUnit[] result = new CodeUnit [units.Count];
310 units.CopyTo (result, 0);
314 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
316 if (partial_types == null)
317 partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
318 return partial_types;
322 Dictionary <string, bool> CodeFiles {
324 if (code_files == null)
325 code_files = new Dictionary <string, bool> ();
330 List <string> SourceFiles {
332 if (source_files == null)
333 source_files = new List <string> ();
338 Dictionary <string, string> ResourceFiles {
340 if (resource_files == null)
341 resource_files = new Dictionary <string, string> ();
342 return resource_files;
346 internal BuildProvider GetBuildProviderForPhysicalFilePath (string path)
348 if (String.IsNullOrEmpty (path) || path_to_buildprovider == null || path_to_buildprovider.Count == 0)
352 if (path_to_buildprovider.TryGetValue (path, out ret))
358 public void AddAssemblyReference (Assembly a)
361 throw new ArgumentNullException ("a");
363 List <Assembly> assemblies = ReferencedAssemblies;
365 if (assemblies.Contains (a))
371 internal void AddAssemblyReference (string assemblyLocation)
374 Assembly asm = Assembly.LoadFrom (assemblyLocation);
378 AddAssemblyReference (asm);
380 // ignore, it will come up later
384 internal void AddAssemblyReference (ICollection asmcoll)
386 if (asmcoll == null || asmcoll.Count == 0)
390 foreach (object o in asmcoll) {
395 AddAssemblyReference (asm);
399 internal void AddAssemblyReference (List <Assembly> asmlist)
404 foreach (Assembly a in asmlist) {
408 AddAssemblyReference (a);
412 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
414 if (compileUnit == null)
415 throw new ArgumentNullException ("compileUnit");
416 units.Add (CheckForPartialTypes (new CodeUnit (null, compileUnit)));
419 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
421 if (buildProvider == null)
422 throw new ArgumentNullException ("buildProvider");
424 if (compileUnit == null)
425 throw new ArgumentNullException ("compileUnit");
427 units.Add (CheckForPartialTypes (new CodeUnit (buildProvider, compileUnit)));
430 void AddPathToBuilderMap (string path, BuildProvider bp)
432 if (path_to_buildprovider == null)
433 path_to_buildprovider = new Dictionary <string, BuildProvider> ();
435 if (path_to_buildprovider.ContainsKey (path))
438 path_to_buildprovider.Add (path, bp);
441 public TextWriter CreateCodeFile (BuildProvider buildProvider)
443 if (buildProvider == null)
444 throw new ArgumentNullException ("buildProvider");
446 // Generate a file name with the correct source language extension
447 string filename = GetTempFilePhysicalPath (provider.FileExtension);
448 SourceFiles.Add (filename);
449 AddPathToBuilderMap (filename, buildProvider);
450 return new StreamWriter (File.OpenWrite (filename));
453 internal void AddCodeFile (string path)
455 AddCodeFile (path, null, false);
458 internal void AddCodeFile (string path, BuildProvider bp)
460 AddCodeFile (path, bp, false);
463 // The kludge of using ICodePragmaGenerator for C# and VB code files is bad, but
464 // it's better than allowing for potential DoS while reading a file with arbitrary
465 // size in memory for use with the CodeSnippetCompileUnit class.
466 // Files with extensions other than .cs and .vb use CodeSnippetCompileUnit.
467 internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
469 if (String.IsNullOrEmpty (path))
472 Dictionary <string, bool> codeFiles = CodeFiles;
473 if (codeFiles.ContainsKey (path))
476 codeFiles.Add (path, true);
478 string extension = Path.GetExtension (path);
479 if (extension == null || extension.Length == 0)
480 return; // maybe better to throw an exception here?
481 extension = extension.Substring (1);
482 string filename = GetTempFilePhysicalPath (extension);
483 ICodePragmaGenerator pragmaGenerator;
485 switch (extension.ToLowerInvariant ()) {
487 pragmaGenerator = new CSharpCodePragmaGenerator ();
491 pragmaGenerator = new VBCodePragmaGenerator ();
495 pragmaGenerator = null;
500 VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
502 throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
504 if (vf is DefaultVirtualFile)
505 path = HostingEnvironment.MapPath (path);
506 CopyFileWithChecksum (vf.Open (), filename, path, pragmaGenerator);
508 CopyFileWithChecksum (path, filename, path, pragmaGenerator);
510 if (pragmaGenerator != null) {
512 AddPathToBuilderMap (filename, bp);
514 SourceFiles.Add (filename);
518 void CopyFileWithChecksum (string input, string to, string from, ICodePragmaGenerator pragmaGenerator)
520 CopyFileWithChecksum (new FileStream (input, FileMode.Open, FileAccess.Read), to, from, pragmaGenerator);
523 void CopyFileWithChecksum (Stream input, string to, string from, ICodePragmaGenerator pragmaGenerator)
525 if (pragmaGenerator == null) {
526 // This is BAD, BAD, BAD! CodeDOM API is really no good in this
529 using (StreamReader sr = new StreamReader (input, WebEncoding.FileEncoding)) {
530 filedata = sr.ReadToEnd ();
533 var snippet = new CodeSnippetCompileUnit (filedata);
534 snippet.LinePragma = new CodeLinePragma (from, 1);
536 AddCodeCompileUnit (snippet);
542 MD5 checksum = MD5.Create ();
543 using (FileStream fs = new FileStream (to, FileMode.Create, FileAccess.Write)) {
544 using (StreamWriter sw = new StreamWriter (fs, Encoding.UTF8)) {
545 using (StreamReader sr = new StreamReader (input, WebEncoding.FileEncoding)) {
546 int count = pragmaGenerator.ReserveSpace (from);
549 if (count > COPY_BUFFER_SIZE)
550 src = new char [count];
552 src = new char [COPY_BUFFER_SIZE];
554 sw.Write (src, 0, count);
556 count = sr.Read (src, 0, COPY_BUFFER_SIZE);
558 UpdateChecksum (src, 0, checksum, true);
562 sw.Write (src, 0, count);
563 UpdateChecksum (src, count, checksum, false);
569 pragmaGenerator.DecorateFile (to, from, checksum, Encoding.UTF8);
572 void UpdateChecksum (char[] buf, int count, MD5 checksum, bool final)
574 byte[] input = Encoding.UTF8.GetBytes (buf, 0, count);
577 checksum.TransformFinalBlock (input, 0, input.Length);
579 checksum.TransformBlock (input, 0, input.Length, input, 0);
583 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
585 if (buildProvider == null)
586 throw new ArgumentNullException ("buildProvider");
588 if (name == null || name == "")
589 throw new ArgumentNullException ("name");
591 string filename = GetTempFilePhysicalPath ("resource");
592 Stream stream = File.OpenWrite (filename);
593 ResourceFiles [name] = filename;
597 [MonoTODO ("Not implemented, does nothing")]
598 public void GenerateTypeFactory (string typeName)
600 // Do nothing by now.
603 public string GetTempFilePhysicalPath (string extension)
605 if (extension == null)
606 throw new ArgumentNullException ("extension");
608 string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
609 temp_files.AddFile (newFileName, KeepFiles);
614 public CodeDomProvider CodeDomProvider {
615 get { return provider; }
618 List <Assembly> ReferencedAssemblies {
620 if (referenced_assemblies == null)
621 referenced_assemblies = new List <Assembly> ();
623 return referenced_assemblies;
627 CodeUnit CheckForPartialTypes (CodeUnit codeUnit)
629 CodeTypeDeclarationCollection types;
630 CompileUnitPartialType partialType;
631 string partialTypeName;
632 List <CompileUnitPartialType> tmp;
633 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
635 foreach (CodeNamespace ns in codeUnit.Unit.Namespaces) {
639 if (types == null || types.Count == 0)
642 foreach (CodeTypeDeclaration type in types) {
646 if (type.IsPartial) {
647 partialType = new CompileUnitPartialType (codeUnit.Unit, ns, type);
648 partialTypeName = partialType.TypeName;
650 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
651 tmp = new List <CompileUnitPartialType> (1);
652 partialTypes.Add (partialTypeName, tmp);
654 tmp.Add (partialType);
662 void ProcessPartialTypes ()
664 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
665 if (partialTypes.Count == 0)
668 foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
669 ProcessType (kvp.Value);
672 void ProcessType (List <CompileUnitPartialType> typeList)
674 CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
677 foreach (CompileUnitPartialType type in typeList) {
684 for (int i = 0; i < counter; i++)
685 CompareTypes (types [i], type);
686 types [counter++] = type;
690 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
692 CodeTypeDeclaration sourceType = source.PartialType;
693 CodeTypeMemberCollection targetMembers = target.PartialType.Members;
694 List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
696 foreach (CodeTypeMember member in targetMembers) {
697 if (TypeHasMember (sourceType, member))
698 membersToRemove.Add (member);
701 foreach (CodeTypeMember member in membersToRemove)
702 targetMembers.Remove (member);
705 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
707 if (type == null || member == null)
710 return (FindMemberByName (type, member.Name) != null);
713 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
715 foreach (CodeTypeMember m in type.Members) {
716 if (m == null || m.Name != name)
724 internal CompilerResults BuildAssembly ()
726 return BuildAssembly (null, CompilerOptions);
729 internal CompilerResults BuildAssembly (VirtualPath virtualPath)
731 return BuildAssembly (virtualPath, CompilerOptions);
734 internal CompilerResults BuildAssembly (CompilerParameters options)
736 return BuildAssembly (null, options);
739 internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
742 throw new ArgumentNullException ("options");
743 options.TempFiles = temp_files;
744 if (options.OutputAssembly == null)
745 options.OutputAssembly = OutputAssemblyName;
747 ProcessPartialTypes ();
749 CompilerResults results;
750 CodeUnit [] units = GetUnitsAsArray ();
752 // Since we may have some source files and some code
753 // units, we generate code from all of them and then
754 // compile the assembly from the set of temporary source
755 // files. This also facilates possible debugging for the
756 // end user, since they get the code beforehand.
757 List <string> files = SourceFiles;
758 Dictionary <string, string> resources = ResourceFiles;
760 if (units.Length == 0 && files.Count == 0 && resources.Count == 0 && options.EmbeddedResources.Count == 0)
763 string compilerOptions = options.CompilerOptions;
764 if (options.IncludeDebugInformation) {
765 if (String.IsNullOrEmpty (compilerOptions))
766 compilerOptions = "/d:DEBUG";
767 else if (compilerOptions.IndexOf ("d:DEBUG", StringComparison.OrdinalIgnoreCase) == -1)
768 compilerOptions += " /d:DEBUG";
770 options.CompilerOptions = compilerOptions;
773 if (String.IsNullOrEmpty (compilerOptions))
774 compilerOptions = "/noconfig";
775 else if (compilerOptions.IndexOf ("noconfig", StringComparison.OrdinalIgnoreCase) == -1)
776 compilerOptions += " /noconfig";
777 options.CompilerOptions = compilerOptions;
780 StreamWriter sw = null;
782 foreach (CodeUnit unit in units) {
783 filename = GetTempFilePhysicalPath (provider.FileExtension);
785 sw = new StreamWriter (File.OpenWrite (filename), Encoding.UTF8);
786 provider.GenerateCodeFromCompileUnit (unit.Unit, sw, null);
787 files.Add (filename);
797 if (unit.BuildProvider != null)
798 AddPathToBuilderMap (filename, unit.BuildProvider);
801 foreach (KeyValuePair <string, string> de in resources)
802 options.EmbeddedResources.Add (de.Value);
804 AddAssemblyReference (BuildManager.GetReferencedAssemblies ());
805 List <Assembly> referencedAssemblies = ReferencedAssemblies;
806 StringCollection optRefAsm = options.ReferencedAssemblies;
807 Type appType = HttpApplicationFactory.AppType;
808 if (appType != null && !referencedAssemblies.Contains (appType.Assembly))
809 referencedAssemblies.Add (appType.Assembly);
811 foreach (Assembly refasm in ReferencedAssemblies) {
812 string path = new Uri (refasm.CodeBase).LocalPath;
813 string originalPath = refasm.Location;
814 if (!optRefAsm.Contains (path) && !optRefAsm.Contains (originalPath))
815 optRefAsm.Add (path);
820 results = provider.CompileAssemblyFromFile (options, files.ToArray ());
822 if (results.NativeCompilerReturnValue != 0) {
823 string fileText = null;
825 using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
826 fileText = sr.ReadToEnd ();
828 } catch (Exception) {}
831 Console.WriteLine ("********************************************************************");
832 Console.WriteLine ("Compilation failed.");
833 Console.WriteLine ("Output:");
834 foreach (string s in results.Output)
835 Console.WriteLine (" " + s);
836 Console.WriteLine ("\nErrors:");
837 foreach (CompilerError err in results.Errors)
838 Console.WriteLine (err);
839 Console.WriteLine ("File name: {0}", results.Errors [0].FileName);
840 Console.WriteLine ("File text:\n{0}\n", fileText);
841 Console.WriteLine ("********************************************************************");
844 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results, fileText);
847 Assembly assembly = results.CompiledAssembly;
848 if (assembly == null) {
849 if (!File.Exists (options.OutputAssembly)) {
850 results.TempFiles.Delete ();
851 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results.Errors,
852 "No assembly returned after compilation!?");
856 results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
857 } catch (Exception ex) {
858 results.TempFiles.Delete ();
859 throw new HttpException ("Unable to load compiled assembly", ex);
864 results.TempFiles.Delete ();