2 // System.Web.Compilation.AppCodeCompiler: A compiler for the App_Code folder
5 // Marek Habersack (grendello@gmail.com)
7 // (C) 2006 Marek Habersack
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Globalization;
39 using System.Web.Configuration;
40 using System.Web.Util;
42 namespace System.Web.Compilation
44 internal class AppCodeAssembly
46 private List<string> files;
50 private bool validAssembly;
54 get { return validAssembly; }
57 public string SourcePath
68 public List<string> Files
74 public AppCodeAssembly (string name, string path)
76 this.files = new List<string>();
77 this.validAssembly = true;
82 public void AddFile (string path)
87 object OnCreateTemporaryAssemblyFile (string path)
89 FileStream f = new FileStream (path, FileMode.CreateNew);
94 // Build and add the assembly to the BuildManager's
95 // CodeAssemblies collection
96 public void Build (string[] binAssemblies)
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>();
104 // First make sure all the files are in the same
107 foreach (string f in files) {
111 extension = Path.GetExtension (f);
112 if (!CodeDomProvider.IsDefinedExtension (extension))
115 language = CodeDomProvider.GetLanguageFromExtension(extension);
116 if (!CodeDomProvider.IsDefinedLanguage (language))
119 if (!known || language == null) {
120 unknownfiles.Add (f);
124 cit = CodeDomProvider.GetCompilerInfo (language);
125 if (cit == null || !cit.IsCodeDomProviderTypeValid)
127 if (compilerProvider == null) {
129 compilerProvider = cit.CodeDomProviderType;
131 } else if (compilerProvider != cit.CodeDomProviderType)
132 throw new HttpException (
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)));
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.");
153 provider = compilerInfo.CreateProvider ();
154 if (provider == null)
155 throw new HttpException ("A code provider error occurred while initializing application.");
157 AssemblyBuilder abuilder = new AssemblyBuilder (provider);
158 foreach (string file in knownfiles)
159 abuilder.AddCodeFile (file);
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);
167 if (compilationSection != null) {
168 foreach (AssemblyInfo ai in compilationSection.Assemblies)
169 if (ai.Assembly != "*")
170 parameters.ReferencedAssemblies.Add (ai.Assembly);
172 BuildProviderCollection buildProviders = compilationSection.BuildProviders;
174 foreach (string file in unknownfiles) {
175 bprovider = GetBuildProviderFor (file, buildProviders);
176 if (bprovider == null)
178 bprovider.GenerateCode (abuilder);
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);
191 if (HttpContext.Current.IsCustomErrorEnabled)
192 throw new HttpException ("An error occurred while initializing application.");
193 throw new CompilationException (null, results.Errors, null);
197 private BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
199 if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
202 BuildProvider ret = buildProviders.GetProviderForExtension (Path.GetExtension (file));
203 if (ret != null && IsCorrectBuilderType (ret)) {
204 ret.SetVirtualPath (VirtualPathUtility.ToAppRelative (file));
211 private bool IsCorrectBuilderType (BuildProvider bp)
218 type = bp.GetType ();
219 attrs = type.GetCustomAttributes (true);
223 BuildProviderAppliesToAttribute bpAppliesTo;
224 bool attributeFound = false;
225 foreach (object attr in attrs) {
226 bpAppliesTo = attr as BuildProviderAppliesToAttribute;
227 if (bpAppliesTo == null)
229 attributeFound = true;
230 if ((bpAppliesTo.AppliesTo & BuildProviderAppliesTo.All) == BuildProviderAppliesTo.All ||
231 (bpAppliesTo.AppliesTo & BuildProviderAppliesTo.Code) == BuildProviderAppliesTo.Code)
242 internal class AppCodeCompiler
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.
251 // Assemblies are named as follows:
253 // 1. main assembly: App_Code.{HASH}
254 // 2. subdir assemblies: App_SubCode_{DirName}.{HASH}
256 // If any of the assemblies contains files that would be
257 // compiled with different compilers, a System.Web.HttpException
260 // Files for which there is no explicit builder are ignored
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;
268 public AppCodeCompiler ()
270 assemblies = new List<AppCodeAssembly>();
273 public void Compile ()
275 string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
276 if (!Directory.Exists (appCode))
279 // First process the codeSubDirectories
280 CompilationSection cs = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
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 (
288 Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
291 AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
292 assemblies.Add (defasm);
293 if (!CollectFiles (appCode, defasm))
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);
305 private bool CollectFiles (string dir, AppCodeAssembly aca)
307 bool haveFiles = false;
309 AppCodeAssembly curaca = aca;
310 foreach (string f in Directory.GetFiles (dir)) {
315 foreach (string d in Directory.GetDirectories (dir)) {
316 foreach (AppCodeAssembly a in assemblies)
317 if (a.SourcePath == d) {
321 CollectFiles (d, curaca);