2 // System.Web.Compilation.AppResourceAseemblyBuilder
5 // Marek Habersack (mhabersack@novell.com)
7 // (C) 2007-2009 Novell, Inc (http://novell.com/)
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.Generic;
35 using System.Collections.Specialized;
36 using System.ComponentModel;
37 using System.Diagnostics;
39 using System.Reflection;
41 using System.Threading;
43 using System.Web.Configuration;
45 namespace System.Web.Compilation
47 class AppResourcesAssemblyBuilder
49 CompilationSection config;
51 CodeDomProvider _provider;
52 string baseAssemblyPath;
53 string baseAssemblyDirectory;
54 string canonicAssemblyName;
55 Assembly mainAssembly;
56 AppResourcesCompiler appResourcesCompiler;
58 public CodeDomProvider Provider {
60 if (_provider == null)
61 _provider = ci.CreateProvider ();
65 if (_provider == null)
66 throw new ApplicationException ("Failed to instantiate the default compiler.");
71 public Assembly MainAssembly {
72 get { return mainAssembly; }
75 public AppResourcesAssemblyBuilder (string canonicAssemblyName, string baseAssemblyPath, AppResourcesCompiler appres)
77 this.appResourcesCompiler = appres;
78 this.baseAssemblyPath = baseAssemblyPath;
79 this.baseAssemblyDirectory = Path.GetDirectoryName (baseAssemblyPath);
80 this.canonicAssemblyName = canonicAssemblyName;
82 config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
83 if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
84 throw new ApplicationException ("Could not get the default compiler.");
85 ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
86 if (ci == null || !ci.IsCodeDomProviderTypeValid)
87 throw new ApplicationException ("Failed to obtain the default compiler information.");
95 public void Build (CodeCompileUnit unit)
97 Dictionary <string, List <string>> cultures = appResourcesCompiler.CultureFiles;
98 List <string> defaultCultureFiles = appResourcesCompiler.DefaultCultureFiles;
100 if (defaultCultureFiles != null)
101 BuildDefaultAssembly (defaultCultureFiles, unit);
103 foreach (KeyValuePair <string, List <string>> kvp in cultures)
104 BuildSatelliteAssembly (kvp.Key, kvp.Value);
107 void BuildDefaultAssembly (List <string> files, CodeCompileUnit unit)
109 AssemblyBuilder abuilder = new AssemblyBuilder (Provider);
111 abuilder.AddCodeCompileUnit (unit);
113 CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
114 cp.OutputAssembly = baseAssemblyPath;
115 cp.GenerateExecutable = false;
116 cp.TreatWarningsAsErrors = true;
117 cp.IncludeDebugInformation = config.Debug;
119 foreach (string f in files)
120 cp.EmbeddedResources.Add (f);
122 CompilerResults results = abuilder.BuildAssembly (cp);
126 if (results.NativeCompilerReturnValue == 0) {
127 mainAssembly = results.CompiledAssembly;
128 BuildManager.TopLevelAssemblies.Add (mainAssembly);
130 if (HttpContext.Current.IsCustomErrorEnabled)
131 throw new ApplicationException ("An error occurred while compiling global resources.");
132 throw new CompilationException (null, results.Errors, null);
135 HttpRuntime.WritePreservationFile (mainAssembly, canonicAssemblyName);
136 HttpRuntime.EnableAssemblyMapping (true);
139 void BuildSatelliteAssembly (string cultureName, List <string> files)
141 string assemblyPath = BuildAssemblyPath (cultureName);
142 var info = new ProcessStartInfo ();
143 var al = new Process ();
145 string arguments = SetAlPath (info);
146 var sb = new StringBuilder (arguments);
148 sb.Append ("/c:\"" + cultureName + "\" ");
149 sb.Append ("/t:lib ");
150 sb.Append ("/out:\"" + assemblyPath + "\" ");
151 if (mainAssembly != null)
152 sb.Append ("/template:\"" + mainAssembly.Location + "\" ");
154 string responseFilePath = assemblyPath + ".response";
155 using (FileStream fs = File.OpenWrite (responseFilePath)) {
156 using (StreamWriter sw = new StreamWriter (fs)) {
157 foreach (string f in files)
158 sw.WriteLine ("/embed:\"" + f + "\" ");
161 sb.Append ("@\"" + responseFilePath + "\"");
163 info.Arguments = sb.ToString ();
164 info.CreateNoWindow = true;
165 info.UseShellExecute = false;
166 info.RedirectStandardOutput = true;
167 info.RedirectStandardError = true;
171 var alOutput = new StringCollection ();
172 var alMutex = new Mutex ();
173 DataReceivedEventHandler outputHandler = (object sender, DataReceivedEventArgs args) => {
174 if (args.Data != null) {
176 alOutput.Add (args.Data);
177 alMutex.ReleaseMutex ();
181 al.ErrorDataReceived += outputHandler;
182 al.OutputDataReceived += outputHandler;
184 // TODO: consider using asynchronous processes
187 } catch (Exception ex) {
188 throw new HttpException (String.Format ("Error running {0}", al.StartInfo.FileName), ex);
191 Exception alException = null;
194 al.BeginOutputReadLine ();
195 al.BeginErrorReadLine ();
197 exitCode = al.ExitCode;
198 } catch (Exception ex) {
201 al.CancelErrorRead ();
202 al.CancelOutputRead ();
206 if (exitCode != 0 || alException != null) {
207 // TODO: consider adding a new type of compilation exception,
209 CompilerErrorCollection errors = null;
211 if (alOutput.Count != 0) {
212 foreach (string line in alOutput) {
213 if (!line.StartsWith ("ALINK: error ", StringComparison.Ordinal))
216 errors = new CompilerErrorCollection ();
218 int colon = line.IndexOf (':', 13);
219 string errorNumber = colon != -1 ? line.Substring (13, colon - 13) : "Unknown";
220 string errorText = colon != -1 ? line.Substring (colon + 1) : line.Substring (13);
222 errors.Add (new CompilerError (Path.GetFileName (assemblyPath), 0, 0, errorNumber, errorText));
226 throw new CompilationException (Path.GetFileName (assemblyPath), errors, null);
230 string SetAlPath (ProcessStartInfo info)
232 if (HttpRuntime.RunningOnWindows) {
235 PropertyInfo gac = typeof (Environment).GetProperty ("GacPath", BindingFlags.Static|BindingFlags.NonPublic);
236 MethodInfo get_gac = gac.GetGetMethod (true);
237 string p = Path.GetDirectoryName ((string) get_gac.Invoke (null, null));
238 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.bat");
239 if (!File.Exists (monoPath)) {
240 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.exe");
241 if (!File.Exists (monoPath)) {
242 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (p))), "mono\\mono\\mini\\mono.exe");
243 if (!File.Exists (monoPath))
244 throw new FileNotFoundException ("Windows mono path not found: " + monoPath);
248 alPath = Path.Combine (p, "2.0\\al.exe");
249 if (!File.Exists (alPath)) {
250 alPath = Path.Combine(Path.GetDirectoryName (p), "lib\\net_2_0\\al.exe");
251 if (!File.Exists (alPath))
252 throw new FileNotFoundException ("Windows al path not found: " + alPath);
255 info.FileName = monoPath;
258 info.FileName = "al2";
263 string BuildAssemblyPath (string cultureName)
265 string baseDir = Path.Combine (baseAssemblyDirectory, cultureName);
266 if (!Directory.Exists (baseDir))
267 Directory.CreateDirectory (baseDir);
269 string baseFileName = Path.GetFileNameWithoutExtension (baseAssemblyPath);
270 string fileName = String.Concat (baseFileName, ".resources.dll");
271 fileName = Path.Combine (baseDir, fileName);
276 CodeCompileUnit GenerateAssemblyInfo (string cultureName)
278 CodeAttributeArgument[] args = new CodeAttributeArgument [1];
279 args [0] = new CodeAttributeArgument (new CodePrimitiveExpression (cultureName));
281 CodeCompileUnit unit = new CodeCompileUnit ();
282 unit.AssemblyCustomAttributes.Add (
283 new CodeAttributeDeclaration (
284 new CodeTypeReference ("System.Reflection.AssemblyCultureAttribute"),
287 args = new CodeAttributeArgument [2];
288 args [0] = new CodeAttributeArgument (new CodePrimitiveExpression ("ASP.NET"));
289 args [1] = new CodeAttributeArgument (new CodePrimitiveExpression (Environment.Version.ToString ()));
290 unit.AssemblyCustomAttributes.Add (
291 new CodeAttributeDeclaration (
292 new CodeTypeReference ("System.CodeDom.Compiler.GeneratedCodeAttribute"),