7ed820d9f0d55727b0934c7db4142657d079cf46
[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.Configuration;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Globalization;
38 using System.IO;
39 using System.Reflection;
40 using System.Web;
41 using System.Web.Configuration;
42 using System.Web.Profile;
43 using System.Web.Util;
44
45 namespace System.Web.Compilation
46 {
47         internal class AppCodeAssembly
48         {
49                 private List<string> files;
50                 private List<CodeCompileUnit> units;
51                 
52                 private string name;
53                 private string path;
54                 private bool validAssembly;
55                 private string outputAssemblyName;
56
57                 public string OutputAssemblyName
58                 {
59                         get {
60                                 return outputAssemblyName;
61                         }
62                 }
63                 
64                 public bool IsValid
65                 {
66                         get { return validAssembly; }
67                 }
68
69                 public string SourcePath
70                 {
71                         get { return path; }
72                 }
73
74 // temporary
75                 public string Name
76                 {
77                         get { return name; }
78                 }
79                 
80                 public List<string> Files
81                 {
82                         get { return files; }
83                 }
84 // temporary
85                 
86                 public AppCodeAssembly (string name, string path)
87                 {
88                         this.files = new List<string> ();
89                         this.units = new List<CodeCompileUnit> ();
90                         this.validAssembly = true;
91                         this.name = name;
92                         this.path = path;
93                 }
94
95                 public void AddFile (string path)
96                 {
97                         files.Add (path);
98                 }
99
100                 public void AddUnit (CodeCompileUnit unit)
101                 {
102                         units.Add (unit);
103                 }
104                 
105                 object OnCreateTemporaryAssemblyFile (string path)
106                 {
107                         FileStream f = new FileStream (path, FileMode.CreateNew);
108                         f.Close ();
109                         return path;
110                 }
111                 
112                 // Build and add the assembly to the BuildManager's
113                 // CodeAssemblies collection
114                 public void Build (string[] binAssemblies)
115                 {
116                         Type compilerProvider = null;
117                         CompilerInfo compilerInfo = null, cit;
118                         string extension, language, cpfile = null;
119                         List<string> knownfiles = new List<string>();
120                         List<string> unknownfiles = new List<string>();
121                         
122                         // First make sure all the files are in the same
123                         // language
124                         bool known;
125                         foreach (string f in files) {
126                                 known = true;
127                                 language = null;
128                                 
129                                 extension = Path.GetExtension (f);
130                                 if (!CodeDomProvider.IsDefinedExtension (extension))
131                                         known = false;
132                                 if (known) {
133                                         language = CodeDomProvider.GetLanguageFromExtension(extension);
134                                         if (!CodeDomProvider.IsDefinedLanguage (language))
135                                                 known = false;
136                                 }
137                                 if (!known || language == null) {
138                                         unknownfiles.Add (f);
139                                         continue;
140                                 }
141                                 
142                                 cit = CodeDomProvider.GetCompilerInfo (language);
143                                 if (cit == null || !cit.IsCodeDomProviderTypeValid)
144                                         continue;
145                                 if (compilerProvider == null) {
146                                         cpfile = f;
147                                         compilerProvider = cit.CodeDomProviderType;
148                                         compilerInfo = cit;
149                                 } else if (compilerProvider != cit.CodeDomProviderType)
150                                         throw new HttpException (
151                                                 String.Format (
152                                                         "Files {0} and {1} are in different languages - they cannot be compiled into the same assembly",
153                                                         Path.GetFileName (cpfile),
154                                                         Path.GetFileName (f)));
155                                 knownfiles.Add (f);
156                         }
157
158                         CodeDomProvider provider = null;
159                         CompilationSection compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
160                         if (compilerInfo == null) {
161                                 if (!CodeDomProvider.IsDefinedLanguage (compilationSection.DefaultLanguage))
162                                         throw new HttpException ("Failed to retrieve default source language");
163                                 compilerInfo = CodeDomProvider.GetCompilerInfo (compilationSection.DefaultLanguage);
164                                 if (compilerInfo == null || !compilerInfo.IsCodeDomProviderTypeValid)
165                                         throw new HttpException ("Internal error while initializing application");
166                                 provider = compilerInfo.CreateProvider ();
167                                 if (provider == null)
168                                         throw new HttpException ("A code provider error occurred while initializing application.");
169                         }
170
171                         provider = compilerInfo.CreateProvider ();
172                         if (provider == null)
173                                 throw new HttpException ("A code provider error occurred while initializing application.");
174
175                         AssemblyBuilder abuilder = new AssemblyBuilder (provider);
176                         foreach (string file in knownfiles)
177                                 abuilder.AddCodeFile (file);
178                         foreach (CodeCompileUnit unit in units)
179                                 abuilder.AddCodeCompileUnit (unit);
180                         
181                         BuildProvider bprovider;
182                         CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
183                         parameters.IncludeDebugInformation = compilationSection.Debug;
184                         
185                         if (binAssemblies != null && binAssemblies.Length > 0)
186                                 parameters.ReferencedAssemblies.AddRange (binAssemblies);
187                         
188                         if (compilationSection != null) {
189                                 AssemblyName asmName;
190                                 foreach (AssemblyInfo ai in compilationSection.Assemblies)
191                                         if (ai.Assembly != "*") {
192                                                 try {
193                                                         asmName = new AssemblyName (ai.Assembly);
194                                                         parameters.ReferencedAssemblies.Add (asmName.Name);
195                                                 } catch (Exception ex) {
196                                                         throw new HttpException (
197                                                                 String.Format ("Could not find assembly {0}.", ai.Assembly),
198                                                                 ex);
199                                                 }
200                                         }
201                                 
202                                 BuildProviderCollection buildProviders = compilationSection.BuildProviders;
203                                 
204                                 foreach (string file in unknownfiles) {
205                                         bprovider = GetBuildProviderFor (file, buildProviders);
206                                         if (bprovider == null)
207                                                 continue;
208                                         bprovider.GenerateCode (abuilder);
209                                 }
210                         }
211                         
212                         outputAssemblyName = (string)FileUtils.CreateTemporaryFile (
213                                 AppDomain.CurrentDomain.SetupInformation.DynamicBase,
214                                 name, "dll", OnCreateTemporaryAssemblyFile);
215                         parameters.OutputAssembly = outputAssemblyName;
216                         foreach (Assembly a in BuildManager.TopLevelAssemblies)
217                                 parameters.ReferencedAssemblies.Add (a.Location);
218                         CompilerResults results = abuilder.BuildAssembly (parameters);
219                         if (results.Errors.Count == 0) {
220                                 BuildManager.CodeAssemblies.Add (results.PathToAssembly);
221                                 BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
222                         } else {
223                                 if (HttpContext.Current.IsCustomErrorEnabled)
224                                         throw new HttpException ("An error occurred while initializing application.");
225                                 throw new CompilationException (null, results.Errors, null);
226                         }
227                 }
228
229                 private BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
230                 {
231                         if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
232                                 return null;
233
234                         BuildProvider ret = buildProviders.GetProviderForExtension (Path.GetExtension (file));
235                         if (ret != null && IsCorrectBuilderType (ret)) {
236                                 ret.SetVirtualPath (VirtualPathUtility.ToAppRelative (file));
237                                 return ret;
238                         }
239                                 
240                         return null;
241                 }
242
243                 private bool IsCorrectBuilderType (BuildProvider bp)
244                 {
245                         if (bp == null)
246                                 return false;
247                         Type type;
248                         object[] attrs;
249
250                         type = bp.GetType ();
251                         attrs = type.GetCustomAttributes (true);
252                         if (attrs == null)
253                                 return false;
254                         
255                         BuildProviderAppliesToAttribute bpAppliesTo;
256                         bool attributeFound = false;
257                         foreach (object attr in attrs) {
258                                 bpAppliesTo = attr as BuildProviderAppliesToAttribute;
259                                 if (bpAppliesTo == null)
260                                         continue;
261                                 attributeFound = true;
262                                 if ((bpAppliesTo.AppliesTo & BuildProviderAppliesTo.All) == BuildProviderAppliesTo.All ||
263                                     (bpAppliesTo.AppliesTo & BuildProviderAppliesTo.Code) == BuildProviderAppliesTo.Code)
264                                         return true;
265                         }
266
267                         if (attributeFound)
268                                 return false;
269                         return true;
270                 }
271                 
272         }
273         
274         internal class AppCodeCompiler
275         {
276                 static private bool _alreadyCompiled;
277                 internal static string DefaultAppCodeAssemblyName;
278                 
279                 // A dictionary that contains an entry per an assembly that will
280                 // be produced by compiling App_Code. There's one main assembly
281                 // and an optional number of assemblies as defined by the
282                 // codeSubDirectories sub-element of the compilation element in
283                 // the system.web section of the app's config file.
284                 // Each entry's value is an AppCodeAssembly instance.
285                 //
286                 // Assemblies are named as follows:
287                 //
288                 //  1. main assembly: App_Code.{HASH}
289                 //  2. subdir assemblies: App_SubCode_{DirName}.{HASH}
290                 //
291                 // If any of the assemblies contains files that would be
292                 // compiled with different compilers, a System.Web.HttpException
293                 // is thrown.
294                 //
295                 // Files for which there is no explicit builder are ignored
296                 // silently
297                 //
298                 // Files for which exist BuildProviders but which have no
299                 // unambiguous language assigned to them (e.g. .wsdl files), are
300                 // built using the default website compiler.
301                 private List<AppCodeAssembly> assemblies;
302                 string _bindir;
303                 
304                 public AppCodeCompiler ()
305                 {
306                         assemblies = new List<AppCodeAssembly>();
307                 }
308
309                 bool ProcessAppCodeDir (string appCode, AppCodeAssembly defasm)
310                 {
311                         // First process the codeSubDirectories
312                         CompilationSection cs = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
313                         
314                         if (cs != null) {
315                                 string aname;
316                                 for (int i = 0; i < cs.CodeSubDirectories.Count; i++) {
317                                         aname = String.Format ("App_SubCode_{0}", cs.CodeSubDirectories[i].DirectoryName);
318                                         assemblies.Add (new AppCodeAssembly (
319                                                                 aname,
320                                                                 Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
321                                 }
322                         }
323                         
324                         return CollectFiles (appCode, defasm);
325                 }
326
327                 CodeTypeReference GetProfilePropertyType (string type)
328                 {
329                         if (String.IsNullOrEmpty (type))
330                                 throw new ArgumentException ("String size cannot be 0", "type");
331                         return new CodeTypeReference (type);
332                 }
333
334                 Type GetTypeFromBin (string typeName)
335                 {
336                         string bindir = BinDir;
337                         if (!Directory.Exists (bindir))
338                                 return null;
339                         
340                         string [] binDlls = Directory.GetFiles (bindir, "*.dll");
341                         Type ret = null;
342                         foreach (string dll in binDlls) {
343                                 try {
344                                         Assembly asm = Assembly.LoadFrom (dll);
345                                         ret = asm.GetType (typeName, false);
346                                         if (ret != null)
347                                                 break;
348                                 } catch (Exception) {
349                                         continue;
350                                 }
351                         }
352
353                         return ret;
354                 }
355
356                 string FindProviderTypeName (ProfileSection ps, string providerName)
357                 {
358                         if (ps.Providers == null || ps.Providers.Count == 0)
359                                 return null;
360                         
361                         ProviderSettings pset = ps.Providers [providerName];
362                         if (pset == null)
363                                 return null;
364                         return pset.Type;
365                 }
366                 
367                 void GetProfileProviderAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
368                                                   string providerName)
369                 {
370                         string providerTypeName;
371
372                         if (String.IsNullOrEmpty (providerName))
373                                 providerTypeName = FindProviderTypeName (ps, ps.DefaultProvider);
374                         else
375                                 providerTypeName = FindProviderTypeName (ps, providerName);
376                         if (providerTypeName == null)
377                                 throw new HttpException (String.Format ("Profile provider type not found: {0}",
378                                                                         providerTypeName));
379                         
380                         Type type = Type.GetType (providerTypeName, false);
381                         if (type == null) {
382                                 type = GetTypeFromBin (providerTypeName);
383                                 if (type == null)
384                                         throw new HttpException (String.Format ("Profile provider type not found: {0}",
385                                                                                 providerTypeName));
386                         }
387                         
388                         collection.Add (
389                                 new CodeAttributeDeclaration (
390                                         "ProfileProvider",
391                                         new CodeAttributeArgument (
392                                                 new CodePrimitiveExpression (providerTypeName)
393                                         )
394                                 )
395                         );
396                 }
397
398                 void GetProfileSettingsSerializeAsAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
399                                                              SerializationMode mode)
400                 {
401                         string parameter = String.Format ("SettingsSerializeAs.{0}", mode.ToString ());
402                         collection.Add (
403                                 new CodeAttributeDeclaration (
404                                         "SettingsSerializeAs",
405                                         new CodeAttributeArgument (
406                                                 new CodeSnippetExpression (parameter)
407                                         )
408                                 )
409                         );
410                                         
411                 }
412                 
413                 void AddProfileClassProperty (ProfileSection ps, CodeTypeDeclaration profileClass, ProfilePropertySettings pset)
414                 {
415                         string name = pset.Name;
416                         if (String.IsNullOrEmpty (name))
417                                 throw new HttpException ("Profile property 'Name' attribute cannot be null.");
418                         CodeMemberProperty property = new CodeMemberProperty ();
419                         string typeName = pset.Type;
420                         if (typeName == "string")
421                                 typeName = "System.String";
422                         property.Name = name;
423                         property.Type = GetProfilePropertyType (typeName);
424                         property.Attributes = MemberAttributes.Public;
425                         
426                         CodeAttributeDeclarationCollection collection = new CodeAttributeDeclarationCollection();
427                         GetProfileProviderAttribute (ps, collection, pset.Provider);
428                         GetProfileSettingsSerializeAsAttribute (ps, collection, pset.SerializeAs);
429
430                         property.CustomAttributes = collection;
431                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
432                         CodeCastExpression cast = new CodeCastExpression ();
433                         ret.Expression = cast;
434
435                         CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
436                                 new CodeThisReferenceExpression (),
437                                 "GetPropertyValue");
438                         CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
439                                 mref,
440                                 new CodeExpression[] { new CodePrimitiveExpression (name) }
441                         );
442                         cast.TargetType = new CodeTypeReference (typeName);
443                         cast.Expression = minvoke;
444                         property.GetStatements.Add (ret);
445
446                         if (!pset.ReadOnly) {
447                                 mref = new CodeMethodReferenceExpression (
448                                         new CodeThisReferenceExpression (),
449                                         "SetPropertyValue");
450                                 minvoke = new CodeMethodInvokeExpression (
451                                         mref,
452                                         new CodeExpression[] { new CodePrimitiveExpression (name), new CodeSnippetExpression ("value") }
453                                 );
454                                 property.SetStatements.Add (minvoke);
455                         }
456                         
457                         
458                         profileClass.Members.Add (property);
459                 }
460
461                 void AddProfileClassGroupProperty (string groupName, string memberName, CodeTypeDeclaration profileClass)
462                 {                       
463                         CodeMemberProperty property = new CodeMemberProperty ();
464                         property.Name = memberName;
465                         property.Type = new CodeTypeReference (groupName);
466                         property.Attributes = MemberAttributes.Public;
467
468                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
469                         CodeCastExpression cast = new CodeCastExpression ();
470                         ret.Expression = cast;
471
472                         CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
473                                 new CodeThisReferenceExpression (),
474                                 "GetProfileGroup");
475                         CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
476                                 mref,
477                                 new CodeExpression[] { new CodePrimitiveExpression (memberName) }
478                         );
479                         cast.TargetType = new CodeTypeReference (groupName);
480                         cast.Expression = minvoke;
481                         property.GetStatements.Add (ret);
482                         
483                         profileClass.Members.Add (property);
484                 }
485                 
486                 void BuildProfileClass (ProfileSection ps, string className, ProfilePropertySettingsCollection psc,
487                                         CodeNamespace ns, string baseClass, SortedList <string, string> groupProperties)
488                 {
489                         CodeTypeDeclaration profileClass = new CodeTypeDeclaration (className);
490                         profileClass.BaseTypes.Add (new CodeTypeReference (baseClass));
491                         profileClass.TypeAttributes = TypeAttributes.Public;
492                         ns.Types.Add (profileClass);
493                         
494                         foreach (ProfilePropertySettings pset in psc)
495                                 AddProfileClassProperty (ps, profileClass, pset);
496                         if (groupProperties != null && groupProperties.Count > 0)
497                                 foreach (KeyValuePair <string, string> group in groupProperties)
498                                         AddProfileClassGroupProperty (group.Key, group.Value, profileClass);
499                 }
500
501                 string MakeGroupName (string name)
502                 {
503                         return String.Format ("ProfileGroup{0}", name);
504                 }
505                 
506                 // FIXME: there should be some validation of syntactic correctness of the member/class name
507                 // for the groups/properties. For now it's left to the compiler to report errors.
508                 //
509                 // CodeGenerator.IsValidLanguageIndependentIdentifier (id) - use that
510                 //
511                 bool ProcessCustomProfile (ProfileSection ps, AppCodeAssembly defasm)
512                 {
513                         CodeCompileUnit unit = new CodeCompileUnit ();
514                         CodeNamespace ns = new CodeNamespace (null);
515                         unit.Namespaces.Add (ns);
516                         defasm.AddUnit (unit);
517                         
518                         ns.Imports.Add (new CodeNamespaceImport ("System"));
519                         ns.Imports.Add (new CodeNamespaceImport ("System.Configuration"));
520                         ns.Imports.Add (new CodeNamespaceImport ("System.Web"));
521                         ns.Imports.Add (new CodeNamespaceImport ("System.Web.Profile"));
522                         
523                         RootProfilePropertySettingsCollection props = ps.PropertySettings;
524                         if (props == null || props.Count == 0)
525                                 return true;
526
527                         SortedList<string, string> groupProperties = new SortedList<string, string> ();
528                         string groupName;
529                         foreach (ProfileGroupSettings pgs in props.GroupSettings) {
530                                 groupName = MakeGroupName (pgs.Name);
531                                 groupProperties.Add (groupName, pgs.Name);
532                                 BuildProfileClass (ps, groupName, pgs.PropertySettings, ns,
533                                                    "System.Web.Profile.ProfileGroupBase", null);
534                         }
535                         
536                         string baseType = ps.Inherits;
537                         if (String.IsNullOrEmpty (baseType))
538                                 baseType = "System.Web.Profile.ProfileBase";
539                         BuildProfileClass (ps, "ProfileCommon", props, ns, baseType, groupProperties);
540                         return true;
541                 }
542
543                 void PutCustomProfileInContext (HttpContext context, string assemblyName)
544                 {
545                         Type type = Type.GetType (String.Format ("ProfileCommon, {0}",
546                                                                  Path.GetFileNameWithoutExtension (assemblyName)));
547                         ProfileBase pb = Activator.CreateInstance (type) as ProfileBase;
548                         if (pb != null)
549                                 context.Profile = pb;
550                 }
551                 
552                 public void Compile ()
553                 {
554                         if (_alreadyCompiled)
555                                 return;
556                         _alreadyCompiled = false;
557                         
558                         string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
559                         ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
560                         bool haveAppCodeDir = Directory.Exists (appCode);
561                         bool haveCustomProfile = ps != null ? ps.PropertySettings.Count > 0 : false;
562                         
563                         if (!haveAppCodeDir && !haveCustomProfile)
564                                 return;
565
566                         AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
567                         assemblies.Add (defasm);
568
569                         bool haveCode = false;
570                         if (haveAppCodeDir)
571                                 haveCode = ProcessAppCodeDir (appCode, defasm);
572                         if (haveCustomProfile)
573                                 if (ProcessCustomProfile (ps, defasm))
574                                         haveCode = true;
575
576                         if (!haveCode)
577                                 return;
578                         
579                         string bindir = BinDir;
580                         string[] binAssemblies = null;
581                         if (Directory.Exists (bindir))
582                                 binAssemblies = Directory.GetFiles (bindir, "*.dll");
583                         foreach (AppCodeAssembly aca in assemblies)
584                                 aca.Build (binAssemblies);
585                         DefaultAppCodeAssemblyName = Path.GetFileNameWithoutExtension (defasm.OutputAssemblyName);
586                 }
587
588                 private bool CollectFiles (string dir, AppCodeAssembly aca)
589                 {
590                         bool haveFiles = false;
591                         
592                         AppCodeAssembly curaca = aca;
593                         foreach (string f in Directory.GetFiles (dir)) {
594                                 aca.AddFile (f);
595                                 haveFiles = true;
596                         }
597                         
598                         foreach (string d in Directory.GetDirectories (dir)) {
599                                 foreach (AppCodeAssembly a in assemblies)
600                                         if (a.SourcePath == d) {
601                                                 curaca = a;
602                                                 break;
603                                         }
604                                 CollectFiles (d, curaca);
605                                 curaca = aca;
606                         }
607                         return haveFiles;
608                 }
609
610                 private string BinDir {
611                         get {
612                                 if (_bindir != null)
613                                         return _bindir;
614                                 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
615                                 _bindir = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
616                                 return _bindir;
617                         }
618                 }
619         }
620 }
621 #endif