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 {
50 class CompileUnitPartialType
52 public readonly CodeCompileUnit Unit;
53 public readonly CodeNamespace ParentNamespace;
54 public readonly CodeTypeDeclaration PartialType;
58 public string TypeName {
60 if (typeName == null) {
61 if (ParentNamespace == null || PartialType == null)
64 typeName = ParentNamespace.Name;
65 if (String.IsNullOrEmpty (typeName))
66 typeName = PartialType.Name;
68 typeName += "." + PartialType.Name;
75 public CompileUnitPartialType (CodeCompileUnit unit, CodeNamespace parentNamespace, CodeTypeDeclaration type)
78 this.ParentNamespace = parentNamespace;
79 this.PartialType = type;
83 public class AssemblyBuilder
87 public readonly BuildProvider BuildProvider;
88 public readonly CodeCompileUnit Unit;
90 public CodeUnit (BuildProvider bp, CodeCompileUnit unit)
92 this.BuildProvider = bp;
97 interface ICodePragmaGenerator
99 int ReserveSpace (string filename);
100 void DecorateFile (string path, string filename, MD5 checksum, Encoding enc);
103 class CSharpCodePragmaGenerator : ICodePragmaGenerator
105 // Copied from CSharpCodeGenerator.cs
106 string QuoteSnippetString (string value)
108 // FIXME: this is weird, but works.
109 string output = value.Replace ("\\", "\\\\");
110 output = output.Replace ("\"", "\\\"");
111 output = output.Replace ("\t", "\\t");
112 output = output.Replace ("\r", "\\r");
113 output = output.Replace ("\n", "\\n");
115 return "\"" + output + "\"";
118 string ChecksumToHex (MD5 checksum)
120 var ret = new StringBuilder ();
121 foreach (byte b in checksum.Hash)
122 ret.Append (b.ToString ("X2"));
124 return ret.ToString ();
127 const int pragmaChecksumStaticCount = 23;
128 const int pragmaLineStaticCount = 8;
129 const int md5ChecksumCount = 32;
131 public int ReserveSpace (string filename)
133 return pragmaChecksumStaticCount +
134 pragmaLineStaticCount +
136 (QuoteSnippetString (filename).Length * 2) +
137 (Environment.NewLine.Length * 3) +
138 BaseCompiler.HashMD5.ToString ("B").Length;
141 public void DecorateFile (string path, string filename, MD5 checksum, Encoding enc)
143 string newline = Environment.NewLine;
144 var sb = new StringBuilder ();
146 sb.AppendFormat ("#pragma checksum {0} \"{1}\" \"{2}\"{3}{3}",
147 QuoteSnippetString (filename),
148 BaseCompiler.HashMD5.ToString ("B"),
149 ChecksumToHex (checksum),
151 sb.AppendFormat ("#line 1 {0}{1}", QuoteSnippetString (filename), newline);
153 byte[] bytes = enc.GetBytes (sb.ToString ());
154 using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Write)) {
155 fs.Seek (enc.GetPreamble ().Length, SeekOrigin.Begin);
156 fs.Write (bytes, 0, bytes.Length);
160 sb.AppendFormat ("{0}#line default{0}#line hidden{0}", newline);
161 bytes = Encoding.UTF8.GetBytes (sb.ToString ());
163 fs.Seek (0, SeekOrigin.End);
164 fs.Write (bytes, 0, bytes.Length);
172 class VBCodePragmaGenerator : ICodePragmaGenerator
174 const int pragmaExternalSourceCount = 21;
175 public int ReserveSpace (string filename)
177 return pragmaExternalSourceCount +
179 (Environment.NewLine.Length);
182 public void DecorateFile (string path, string filename, MD5 checksum, Encoding enc)
184 string newline = Environment.NewLine;
185 var sb = new StringBuilder ();
187 sb.AppendFormat ("#ExternalSource(\"{0}\",1){1}", filename, newline);
188 byte[] bytes = enc.GetBytes (sb.ToString ());
189 using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Write)) {
190 fs.Seek (enc.GetPreamble ().Length, SeekOrigin.Begin);
191 fs.Write (bytes, 0, bytes.Length);
195 sb.AppendFormat ("{0}#End ExternalSource{0}", newline);
196 bytes = enc.GetBytes (sb.ToString ());
197 fs.Seek (0, SeekOrigin.End);
198 fs.Write (bytes, 0, bytes.Length);
205 const string DEFAULT_ASSEMBLY_BASE_NAME = "App_Web_";
206 const int COPY_BUFFER_SIZE = 8192;
208 static bool KeepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
210 CodeDomProvider provider;
211 CompilerParameters parameters;
213 Dictionary <string, bool> code_files;
214 Dictionary <string, List <CompileUnitPartialType>> partial_types;
215 Dictionary <string, BuildProvider> path_to_buildprovider;
216 List <CodeUnit> units;
217 List <string> source_files;
218 List <Assembly> referenced_assemblies;
219 Dictionary <string, string> resource_files;
220 TempFileCollection temp_files;
221 string outputFilesPrefix;
222 string outputAssemblyPrefix;
223 string outputAssemblyName;
225 internal AssemblyBuilder (CodeDomProvider provider)
226 : this (null, provider, DEFAULT_ASSEMBLY_BASE_NAME)
229 internal AssemblyBuilder (CodeDomProvider provider, string assemblyBaseName)
230 : this (null, provider, assemblyBaseName)
233 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider)
234 : this (virtualPath, provider, DEFAULT_ASSEMBLY_BASE_NAME)
237 internal AssemblyBuilder (VirtualPath virtualPath, CodeDomProvider provider, string assemblyBaseName)
239 this.provider = provider;
240 this.outputFilesPrefix = assemblyBaseName ?? DEFAULT_ASSEMBLY_BASE_NAME;
242 units = new List <CodeUnit> ();
244 CompilationSection section;
245 section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
246 string tempdir = section.TempDirectory;
247 if (String.IsNullOrEmpty (tempdir))
248 tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
251 KeepFiles = section.Debug;
253 temp_files = new TempFileCollection (tempdir, KeepFiles);
256 internal string OutputFilesPrefix {
258 if (outputFilesPrefix == null)
259 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
261 return outputFilesPrefix;
265 if (String.IsNullOrEmpty (value))
266 outputFilesPrefix = DEFAULT_ASSEMBLY_BASE_NAME;
268 outputFilesPrefix = value;
269 outputAssemblyPrefix = null;
270 outputAssemblyName = null;
274 internal string OutputAssemblyPrefix {
276 if (outputAssemblyPrefix == null) {
277 string basePath = temp_files.BasePath;
278 string baseName = Path.GetFileName (basePath);
279 string baseDir = Path.GetDirectoryName (basePath);
281 outputAssemblyPrefix = Path.Combine (baseDir, String.Concat (OutputFilesPrefix, baseName));
284 return outputAssemblyPrefix;
288 internal string OutputAssemblyName {
290 if (outputAssemblyName == null)
291 outputAssemblyName = OutputAssemblyPrefix + ".dll";
293 return outputAssemblyName;
297 internal TempFileCollection TempFiles {
298 get { return temp_files; }
301 internal CompilerParameters CompilerOptions {
302 get { return parameters; }
303 set { parameters = value; }
306 CodeUnit[] GetUnitsAsArray ()
308 CodeUnit[] result = new CodeUnit [units.Count];
309 units.CopyTo (result, 0);
313 internal Dictionary <string, List <CompileUnitPartialType>> PartialTypes {
315 if (partial_types == null)
316 partial_types = new Dictionary <string, List <CompileUnitPartialType>> ();
317 return partial_types;
321 Dictionary <string, bool> CodeFiles {
323 if (code_files == null)
324 code_files = new Dictionary <string, bool> ();
329 List <string> SourceFiles {
331 if (source_files == null)
332 source_files = new List <string> ();
337 Dictionary <string, string> ResourceFiles {
339 if (resource_files == null)
340 resource_files = new Dictionary <string, string> ();
341 return resource_files;
345 internal BuildProvider GetBuildProviderForPhysicalFilePath (string path)
347 if (String.IsNullOrEmpty (path) || path_to_buildprovider == null || path_to_buildprovider.Count == 0)
351 if (path_to_buildprovider.TryGetValue (path, out ret))
357 public void AddAssemblyReference (Assembly a)
360 throw new ArgumentNullException ("a");
362 List <Assembly> assemblies = ReferencedAssemblies;
364 if (assemblies.Contains (a))
370 internal void AddAssemblyReference (string assemblyLocation)
373 Assembly asm = Assembly.LoadFrom (assemblyLocation);
377 AddAssemblyReference (asm);
379 // ignore, it will come up later
383 internal void AddAssemblyReference (ICollection asmcoll)
385 if (asmcoll == null || asmcoll.Count == 0)
389 foreach (object o in asmcoll) {
394 AddAssemblyReference (asm);
398 internal void AddAssemblyReference (List <Assembly> asmlist)
403 foreach (Assembly a in asmlist) {
407 AddAssemblyReference (a);
411 internal void AddCodeCompileUnit (CodeCompileUnit compileUnit)
413 if (compileUnit == null)
414 throw new ArgumentNullException ("compileUnit");
415 units.Add (CheckForPartialTypes (new CodeUnit (null, compileUnit)));
418 public void AddCodeCompileUnit (BuildProvider buildProvider, CodeCompileUnit compileUnit)
420 if (buildProvider == null)
421 throw new ArgumentNullException ("buildProvider");
423 if (compileUnit == null)
424 throw new ArgumentNullException ("compileUnit");
426 units.Add (CheckForPartialTypes (new CodeUnit (buildProvider, compileUnit)));
429 void AddPathToBuilderMap (string path, BuildProvider bp)
431 if (path_to_buildprovider == null)
432 path_to_buildprovider = new Dictionary <string, BuildProvider> ();
434 if (path_to_buildprovider.ContainsKey (path))
437 path_to_buildprovider.Add (path, bp);
440 public TextWriter CreateCodeFile (BuildProvider buildProvider)
442 if (buildProvider == null)
443 throw new ArgumentNullException ("buildProvider");
445 // Generate a file name with the correct source language extension
446 string filename = GetTempFilePhysicalPath (provider.FileExtension);
447 SourceFiles.Add (filename);
448 AddPathToBuilderMap (filename, buildProvider);
449 return new StreamWriter (File.OpenWrite (filename));
452 internal void AddCodeFile (string path)
454 AddCodeFile (path, null, false);
457 internal void AddCodeFile (string path, BuildProvider bp)
459 AddCodeFile (path, bp, false);
462 // The kludge of using ICodePragmaGenerator for C# and VB code files is bad, but
463 // it's better than allowing for potential DoS while reading a file with arbitrary
464 // size in memory for use with the CodeSnippetCompileUnit class.
465 // Files with extensions other than .cs and .vb use CodeSnippetCompileUnit.
466 internal void AddCodeFile (string path, BuildProvider bp, bool isVirtual)
468 if (String.IsNullOrEmpty (path))
471 Dictionary <string, bool> codeFiles = CodeFiles;
472 if (codeFiles.ContainsKey (path))
475 codeFiles.Add (path, true);
477 string extension = Path.GetExtension (path);
478 if (extension == null || extension.Length == 0)
479 return; // maybe better to throw an exception here?
480 extension = extension.Substring (1);
481 string filename = GetTempFilePhysicalPath (extension);
482 ICodePragmaGenerator pragmaGenerator;
484 switch (extension.ToLower ()) {
486 pragmaGenerator = new CSharpCodePragmaGenerator ();
490 pragmaGenerator = new VBCodePragmaGenerator ();
494 pragmaGenerator = null;
499 VirtualFile vf = HostingEnvironment.VirtualPathProvider.GetFile (path);
501 throw new HttpException (404, "Virtual file '" + path + "' does not exist.");
503 if (vf is DefaultVirtualFile)
504 path = HostingEnvironment.MapPath (path);
505 CopyFileWithChecksum (vf.Open (), filename, path, pragmaGenerator);
507 CopyFileWithChecksum (path, filename, path, pragmaGenerator);
509 if (pragmaGenerator != null) {
511 AddPathToBuilderMap (filename, bp);
513 SourceFiles.Add (filename);
517 void CopyFileWithChecksum (string input, string to, string from, ICodePragmaGenerator pragmaGenerator)
519 CopyFileWithChecksum (new FileStream (input, FileMode.Open, FileAccess.Read), to, from, pragmaGenerator);
522 void CopyFileWithChecksum (Stream input, string to, string from, ICodePragmaGenerator pragmaGenerator)
524 if (pragmaGenerator == null) {
525 // This is BAD, BAD, BAD! CodeDOM API is really no good in this
528 using (StreamReader sr = new StreamReader (input, WebEncoding.FileEncoding)) {
529 filedata = sr.ReadToEnd ();
532 var snippet = new CodeSnippetCompileUnit (filedata);
533 snippet.LinePragma = new CodeLinePragma (from, 1);
535 AddCodeCompileUnit (snippet);
541 MD5 checksum = MD5.Create ();
542 using (FileStream fs = new FileStream (to, FileMode.Create, FileAccess.Write)) {
543 using (StreamWriter sw = new StreamWriter (fs, Encoding.UTF8)) {
544 using (StreamReader sr = new StreamReader (input, WebEncoding.FileEncoding)) {
545 int count = pragmaGenerator.ReserveSpace (from);
548 if (count > COPY_BUFFER_SIZE)
549 src = new char [count];
551 src = new char [COPY_BUFFER_SIZE];
553 sw.Write (src, 0, count);
555 count = sr.Read (src, 0, COPY_BUFFER_SIZE);
557 UpdateChecksum (src, 0, checksum, true);
561 sw.Write (src, 0, count);
562 UpdateChecksum (src, count, checksum, false);
568 pragmaGenerator.DecorateFile (to, from, checksum, Encoding.UTF8);
571 void UpdateChecksum (char[] buf, int count, MD5 checksum, bool final)
573 byte[] input = Encoding.UTF8.GetBytes (buf, 0, count);
576 checksum.TransformFinalBlock (input, 0, input.Length);
578 checksum.TransformBlock (input, 0, input.Length, input, 0);
582 public Stream CreateEmbeddedResource (BuildProvider buildProvider, string name)
584 if (buildProvider == null)
585 throw new ArgumentNullException ("buildProvider");
587 if (name == null || name == "")
588 throw new ArgumentNullException ("name");
590 string filename = GetTempFilePhysicalPath ("resource");
591 Stream stream = File.OpenWrite (filename);
592 ResourceFiles [name] = filename;
596 [MonoTODO ("Not implemented, does nothing")]
597 public void GenerateTypeFactory (string typeName)
599 // Do nothing by now.
602 public string GetTempFilePhysicalPath (string extension)
604 if (extension == null)
605 throw new ArgumentNullException ("extension");
607 string newFileName = OutputAssemblyPrefix + "_" + temp_files.Count + "." + extension;
608 temp_files.AddFile (newFileName, KeepFiles);
613 public CodeDomProvider CodeDomProvider {
614 get { return provider; }
617 List <Assembly> ReferencedAssemblies {
619 if (referenced_assemblies == null)
620 referenced_assemblies = new List <Assembly> ();
622 return referenced_assemblies;
626 CodeUnit CheckForPartialTypes (CodeUnit codeUnit)
628 CodeTypeDeclarationCollection types;
629 CompileUnitPartialType partialType;
630 string partialTypeName;
631 List <CompileUnitPartialType> tmp;
632 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
634 foreach (CodeNamespace ns in codeUnit.Unit.Namespaces) {
638 if (types == null || types.Count == 0)
641 foreach (CodeTypeDeclaration type in types) {
645 if (type.IsPartial) {
646 partialType = new CompileUnitPartialType (codeUnit.Unit, ns, type);
647 partialTypeName = partialType.TypeName;
649 if (!partialTypes.TryGetValue (partialTypeName, out tmp)) {
650 tmp = new List <CompileUnitPartialType> (1);
651 partialTypes.Add (partialTypeName, tmp);
653 tmp.Add (partialType);
661 void ProcessPartialTypes ()
663 Dictionary <string, List <CompileUnitPartialType>> partialTypes = PartialTypes;
664 if (partialTypes.Count == 0)
667 foreach (KeyValuePair <string, List <CompileUnitPartialType>> kvp in partialTypes)
668 ProcessType (kvp.Value);
671 void ProcessType (List <CompileUnitPartialType> typeList)
673 CompileUnitPartialType[] types = new CompileUnitPartialType [typeList.Count];
676 foreach (CompileUnitPartialType type in typeList) {
683 for (int i = 0; i < counter; i++)
684 CompareTypes (types [i], type);
685 types [counter++] = type;
689 void CompareTypes (CompileUnitPartialType source, CompileUnitPartialType target)
691 CodeTypeDeclaration sourceType = source.PartialType;
692 CodeTypeMemberCollection targetMembers = target.PartialType.Members;
693 List <CodeTypeMember> membersToRemove = new List <CodeTypeMember> ();
695 foreach (CodeTypeMember member in targetMembers) {
696 if (TypeHasMember (sourceType, member))
697 membersToRemove.Add (member);
700 foreach (CodeTypeMember member in membersToRemove)
701 targetMembers.Remove (member);
704 bool TypeHasMember (CodeTypeDeclaration type, CodeTypeMember member)
706 if (type == null || member == null)
709 return (FindMemberByName (type, member.Name) != null);
712 CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
714 foreach (CodeTypeMember m in type.Members) {
715 if (m == null || m.Name != name)
723 internal CompilerResults BuildAssembly ()
725 return BuildAssembly (null, CompilerOptions);
728 internal CompilerResults BuildAssembly (VirtualPath virtualPath)
730 return BuildAssembly (virtualPath, CompilerOptions);
733 internal CompilerResults BuildAssembly (CompilerParameters options)
735 return BuildAssembly (null, options);
738 internal CompilerResults BuildAssembly (VirtualPath virtualPath, CompilerParameters options)
741 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 if (options.IncludeDebugInformation) {
764 string compilerOptions = options.CompilerOptions;
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;
774 StreamWriter sw = null;
776 foreach (CodeUnit unit in units) {
777 filename = GetTempFilePhysicalPath (provider.FileExtension);
779 sw = new StreamWriter (File.OpenWrite (filename), Encoding.UTF8);
780 provider.GenerateCodeFromCompileUnit (unit.Unit, sw, null);
781 files.Add (filename);
791 if (unit.BuildProvider != null)
792 AddPathToBuilderMap (filename, unit.BuildProvider);
795 foreach (KeyValuePair <string, string> de in resources)
796 options.EmbeddedResources.Add (de.Value);
798 AddAssemblyReference (BuildManager.GetReferencedAssemblies ());
799 foreach (Assembly refasm in ReferencedAssemblies) {
800 string path = new Uri (refasm.CodeBase).LocalPath;
801 options.ReferencedAssemblies.Add (path);
804 results = provider.CompileAssemblyFromFile (options, files.ToArray ());
806 if (results.NativeCompilerReturnValue != 0) {
807 string fileText = null;
809 using (StreamReader sr = File.OpenText (results.Errors [0].FileName)) {
810 fileText = sr.ReadToEnd ();
812 } catch (Exception) {}
815 Console.WriteLine ("********************************************************************");
816 Console.WriteLine ("Compilation failed.");
817 Console.WriteLine ("Output:");
818 foreach (string s in results.Output)
819 Console.WriteLine (" " + s);
820 Console.WriteLine ("\nErrors:");
821 foreach (CompilerError err in results.Errors)
822 Console.WriteLine (err);
823 Console.WriteLine ("File name: {0}", results.Errors [0].FileName);
824 Console.WriteLine ("File text:\n{0}\n", fileText);
825 Console.WriteLine ("********************************************************************");
828 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results, fileText);
831 Assembly assembly = results.CompiledAssembly;
832 if (assembly == null) {
833 if (!File.Exists (options.OutputAssembly)) {
834 results.TempFiles.Delete ();
835 throw new CompilationException (virtualPath != null ? virtualPath.Original : String.Empty, results.Errors,
836 "No assembly returned after compilation!?");
840 results.CompiledAssembly = Assembly.LoadFrom (options.OutputAssembly);
841 } catch (Exception ex) {
842 results.TempFiles.Delete ();
843 throw new HttpException ("Unable to load compiled assembly", ex);
848 results.TempFiles.Delete ();