New test.
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppCodeCompiler.cs
1 //
2 // System.Web.Compilation.AppCodeCompiler: A compiler for the App_Code folder
3 //
4 // Authors:
5 //   Marek Habersack (grendello@gmail.com)
6 //
7 // (C) 2006 Marek Habersack
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if NET_2_0
31 using System;
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Globalization;
37 using System.IO;
38 using System.Web;
39 using System.Web.Configuration;
40 using System.Web.Util;
41
42 namespace System.Web.Compilation
43 {
44         internal class AppCodeAssembly
45         {
46                 private List<string> files;
47
48                 private string name;
49                 private string path;
50                 private bool validAssembly;
51                 
52                 public bool IsValid
53                 {
54                         get { return validAssembly; }
55                 }
56
57                 public string SourcePath
58                 {
59                         get { return path; }
60                 }
61
62 // temporary
63                 public string Name
64                 {
65                         get { return name; }
66                 }
67                 
68                 public List<string> Files
69                 {
70                         get { return files; }
71                 }
72 // temporary
73                 
74                 public AppCodeAssembly (string name, string path)
75                 {
76                         this.files = new List<string>();
77                         this.validAssembly = true;
78                         this.name = name;
79                         this.path = path;
80                 }
81
82                 public void AddFile (string path)
83                 {
84                         files.Add (path);
85                 }
86
87                 object OnCreateTemporaryAssemblyFile (string path)
88                 {
89                         FileStream f = new FileStream (path, FileMode.CreateNew);
90                         f.Close ();
91                         return path;
92                 }
93                 
94                 // Build and add the assembly to the BuildManager's
95                 // CodeAssemblies collection
96                 public void Build (string[] binAssemblies)
97                 {
98                         Type compilerProvider = null;
99                         CompilerInfo compilerInfo = null, cit;
100                         string extension, language, cpfile = null;
101                         List<string> knownfiles = new List<string>();
102                         List<string> unknownfiles = new List<string>();
103                         
104                         // First make sure all the files are in the same
105                         // language
106                         bool known;
107                         foreach (string f in files) {
108                                 known = true;
109                                 language = null;
110                                 
111                                 extension = Path.GetExtension (f);
112                                 if (!CodeDomProvider.IsDefinedExtension (extension))
113                                         known = false;
114                                 if (known) {
115                                         language = CodeDomProvider.GetLanguageFromExtension(extension);
116                                         if (!CodeDomProvider.IsDefinedLanguage (language))
117                                                 known = false;
118                                 }
119                                 if (!known || language == null) {
120                                         unknownfiles.Add (f);
121                                         continue;
122                                 }
123                                 
124                                 cit = CodeDomProvider.GetCompilerInfo (language);
125                                 if (cit == null || !cit.IsCodeDomProviderTypeValid)
126                                         continue;
127                                 if (compilerProvider == null) {
128                                         cpfile = f;
129                                         compilerProvider = cit.CodeDomProviderType;
130                                         compilerInfo = cit;
131                                 } else if (compilerProvider != cit.CodeDomProviderType)
132                                         throw new HttpException (
133                                                 String.Format (
134                                                         "Files {0} and {1} are in different languages - they cannot be compiled into the same assembly",
135                                                         Path.GetFileName (cpfile),
136                                                         Path.GetFileName (f)));
137                                 knownfiles.Add (f);
138                         }
139
140                         CodeDomProvider provider = null;
141                         if (compilerInfo == null) {
142                                 CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
143                                 if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
144                                         throw new HttpException ("Failed to retrieve default source language");
145                                 compilerInfo = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
146                                 if (compilerInfo == null || !compilerInfo.IsCodeDomProviderTypeValid)
147                                         throw new HttpException ("Internal error while initializing application");
148                                 provider = compilerInfo.CreateProvider ();
149                                 if (provider == null)
150                                         throw new HttpException ("A code provider error occurred while initializing application.");
151                         }
152
153                         provider = compilerInfo.CreateProvider ();
154                         if (provider == null)
155                                 throw new HttpException ("A code provider error occurred while initializing application.");
156
157                         AssemblyBuilder abuilder = new AssemblyBuilder (provider);
158                         foreach (string file in knownfiles)
159                                 abuilder.AddCodeFile (file);
160                         
161                         BuildProvider bprovider;
162                         CompilationSection compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
163                         CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
164                         if (binAssemblies != null && binAssemblies.Length > 0)
165                                 parameters.ReferencedAssemblies.AddRange (binAssemblies);
166                         
167                         if (compilationSection != null) {
168                                 foreach (AssemblyInfo ai in compilationSection.Assemblies)
169                                         if (ai.Assembly != "*")
170                                                 parameters.ReferencedAssemblies.Add (ai.Assembly);
171                                 
172                                 BuildProviderCollection buildProviders = compilationSection.BuildProviders;
173                                 
174                                 foreach (string file in unknownfiles) {
175                                         bprovider = GetBuildProviderFor (file, buildProviders);
176                                         if (bprovider == null)
177                                                 continue;
178                                         bprovider.GenerateCode (abuilder);
179                                 }
180                         }
181                         
182                         string assemblyName = (string)FileUtils.CreateTemporaryFile (
183                                 AppDomain.CurrentDomain.SetupInformation.DynamicBase,
184                                 name, "dll", OnCreateTemporaryAssemblyFile);
185                         parameters.OutputAssembly = assemblyName;
186                         CompilerResults results = abuilder.BuildAssembly (parameters);
187                         if (results.Errors.Count == 0) {
188                                 BuildManager.CodeAssemblies.Add (results.PathToAssembly);
189                                 BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
190                         } else {
191                                 if (HttpContext.Current.IsCustomErrorEnabled)
192                                         throw new HttpException ("An error occurred while initializing application.");
193                                 throw new CompilationException (null, results.Errors, null);
194                         }
195                 }
196
197                 private BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
198                 {
199                         if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
200                                 return null;
201
202                         BuildProvider ret = buildProviders.GetProviderForExtension (Path.GetExtension (file));
203                         if (ret != null && IsCorrectBuilderType (ret)) {
204                                 ret.SetVirtualPath (VirtualPathUtility.ToAppRelative (file));
205                                 return ret;
206                         }
207                                 
208                         return null;
209                 }
210
211                 private bool IsCorrectBuilderType (BuildProvider bp)
212                 {
213                         if (bp == null)
214                                 return false;
215                         Type type;
216                         object[] attrs;
217
218                         type = bp.GetType ();
219                         attrs = type.GetCustomAttributes (true);
220                         if (attrs == null)
221                                 return false;
222                         
223                         BuildProviderAppliesToAttribute bpAppliesTo;
224                         bool attributeFound = false;
225                         foreach (object attr in attrs) {
226                                 bpAppliesTo = attr as BuildProviderAppliesToAttribute;
227                                 if (bpAppliesTo == null)
228                                         continue;
229                                 attributeFound = true;
230                                 if ((bpAppliesTo.AppliesTo & BuildProviderAppliesTo.All) == BuildProviderAppliesTo.All ||
231                                     (bpAppliesTo.AppliesTo & BuildProviderAppliesTo.Code) == BuildProviderAppliesTo.Code)
232                                         return true;
233                         }
234
235                         if (attributeFound)
236                                 return false;
237                         return true;
238                 }
239                 
240         }
241         
242         internal class AppCodeCompiler
243         {
244                 // A dictionary that contains an entry per an assembly that will
245                 // be produced by compiling App_Code. There's one main assembly
246                 // and an optional number of assemblies as defined by the
247                 // codeSubDirectories sub-element of the compilation element in
248                 // the system.web section of the app's config file.
249                 // Each entry's value is an AppCodeAssembly instance.
250                 //
251                 // Assemblies are named as follows:
252                 //
253                 //  1. main assembly: App_Code.{HASH}
254                 //  2. subdir assemblies: App_SubCode_{DirName}.{HASH}
255                 //
256                 // If any of the assemblies contains files that would be
257                 // compiled with different compilers, a System.Web.HttpException
258                 // is thrown.
259                 //
260                 // Files for which there is no explicit builder are ignored
261                 // silently
262                 //
263                 // Files for which exist BuildProviders but which have no
264                 // unambiguous language assigned to them (e.g. .wsdl files), are
265                 // built using the default website compiler.
266                 private List<AppCodeAssembly> assemblies;
267                 
268                 public AppCodeCompiler ()
269                 {
270                         assemblies = new List<AppCodeAssembly>();
271                 }
272
273                 public void Compile ()
274                 {
275                         string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
276                         if (!Directory.Exists (appCode))
277                                 return;
278                         
279                         // First process the codeSubDirectories
280                         CompilationSection cs = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
281                         
282                         if (cs != null) {
283                                 string aname;
284                                 for (int i = 0; i < cs.CodeSubDirectories.Count; i++) {
285                                         aname = String.Format ("App_SubCode_{0}", cs.CodeSubDirectories[i].DirectoryName);
286                                         assemblies.Add (new AppCodeAssembly (
287                                                                 aname,
288                                                                 Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
289                                 }
290                         }
291                         AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
292                         assemblies.Add (defasm);
293                         if (!CollectFiles (appCode, defasm))
294                                 return;
295
296                         AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
297                         string bindir = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
298                         string[] binAssemblies = null;
299                         if (Directory.Exists (bindir))
300                                 binAssemblies = Directory.GetFiles (bindir, "*.dll");
301                         foreach (AppCodeAssembly aca in assemblies)
302                                 aca.Build (binAssemblies);
303                 }
304
305                 private bool CollectFiles (string dir, AppCodeAssembly aca)
306                 {
307                         bool haveFiles = false;
308                         
309                         AppCodeAssembly curaca = aca;
310                         foreach (string f in Directory.GetFiles (dir)) {
311                                 aca.AddFile (f);
312                                 haveFiles = true;
313                         }
314                         
315                         foreach (string d in Directory.GetDirectories (dir)) {
316                                 foreach (AppCodeAssembly a in assemblies)
317                                         if (a.SourcePath == d) {
318                                                 curaca = a;
319                                                 break;
320                                         }
321                                 CollectFiles (d, curaca);
322                                 curaca = aca;
323                         }
324                         return haveFiles;
325                 }
326         }
327 }
328 #endif